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

Yannick Lecaillez
18.22.2016 f2b5fa18b58db09562d03a7d247e21c111e78056
OPENDJ-3179: Migrate LDAP Connection Handler to SDK Grizzly transport

Add a new LDAPConnectionHandler based on Grizzly and using the reactive API
22 files added
33 files modified
8679 ■■■■ changed files
opendj-core/clirr-ignored-api-changes.xml 80 ●●●●● patch | view | raw | blame | history
opendj-core/pom.xml 15 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/Action.java 29 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/Completable.java 58 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/Consumer.java 34 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/ReactiveFilter.java 185 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/ReactiveHandler.java 41 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java 310 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/Single.java 96 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/Stream.java 81 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/reactive/package-info.java 19 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java 2 ●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPClientContext.java 72 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java 4 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java 61 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPListenerImpl.java 11 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LdapMessages.java 157 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/TransportProvider.java 12 ●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java 10 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java 57 ●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPListener.java 17 ●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicTransportProvider.java 10 ●●●● patch | view | raw | blame | history
opendj-grizzly/pom.xml 4 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyTransportProvider.java 144 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ServerConnectionAdaptor.java 154 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferReader.java 44 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferWriter.java 170 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java 9 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java 20 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java 118 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java 34 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPBaseFilter.java 72 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java 81 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java 1183 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapCodec.java 175 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java 77 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslFilter.java 98 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslUtils.java 44 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferReaderTestCase.java 9 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java 12 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java 37 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java 39 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java 75 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java 17 ●●●● patch | view | raw | blame | history
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/pom.xml 18 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java 2 ●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Converters.java 301 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Components.java 271 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java 1944 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java 1116 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/ProxyToHandler.java 168 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Route.java 61 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/package-info.java 19 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/LdapEndpointConfigManager.java 798 ●●●●● patch | view | raw | blame | history
opendj-core/clirr-ignored-api-changes.xml
@@ -139,4 +139,84 @@
    <method>java.util.Collection getObjectClassesWithName(java.lang.String)</method>
    <justification>No duplicate names allowed</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/LDAPClientContext</className>
    <differenceType>7002</differenceType>
    <method>void enableConnectionSecurityLayer(org.forgerock.opendj.ldap.ConnectionSecurityLayer)</method>
    <justification>Security layer now handled with enableTLS() and enableSASL()</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/LDAPClientContext</className>
    <differenceType>7012</differenceType>
    <method>void enableSASL(javax.security.sasl.SaslServer)</method>
    <justification>Simplify management of security layer</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/LDAPClientContext</className>
    <differenceType>7004</differenceType>
    <method>void enableTLS(javax.net.ssl.SSLContext, java.lang.String[], java.lang.String[], boolean, boolean)</method>
    <justification>Simplify management of security layer</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/LDAPClientContext</className>
    <differenceType>7012</differenceType>
    <method>void onDisconnect(org.forgerock.opendj.ldap.LDAPClientContext$DisconnectListener)</method>
    <justification>Allows to register connection state listener</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/LDAPListener</className>
    <differenceType>7005</differenceType>
    <method>LDAPListener(java.net.InetSocketAddress, org.forgerock.opendj.ldap.ServerConnectionFactory, org.forgerock.util.Options)</method>
    <to>%regex[LDAPListener\(java\.net\.SocketAddress,\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory,\s*org\.forgerock\.util\.Options\)]</to>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/spi/LDAPListenerImpl</className>
    <differenceType>7002</differenceType>
    <method>java.net.InetSocketAddress getSocketAddress()</method>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/spi/LDAPListenerImpl</className>
    <differenceType>7012</differenceType>
    <method>java.util.Set getSocketAddresses()</method>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/LDAPListener</className>
    <differenceType>7002</differenceType>
    <method>java.net.InetSocketAddress getSocketAddress()</method>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
   <difference>
    <className>org/forgerock/opendj/ldap/LDAPListener</className>
    <differenceType>7012</differenceType>
    <method>java.util.Set getSocketAddresses()</method>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
    <difference>
    <className>org/forgerock/opendj/ldap/LDAPListener</className>
    <differenceType>7002</differenceType>
    <method>java.net.InetAddress getAddress()</method>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/LDAPListener</className>
    <differenceType>7002</differenceType>
    <method>java.lang.String getHostName()</method>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/LDAPListener</className>
    <differenceType>7002</differenceType>
    <method>int getPort()</method>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/spi/TransportProvider</className>
    <differenceType>7005</differenceType>
    <method>org.forgerock.opendj.ldap.spi.LDAPListenerImpl getLDAPListener(java.net.InetSocketAddress, org.forgerock.opendj.ldap.ServerConnectionFactory, org.forgerock.util.Options)</method>
    <to>%regex[org\.forgerock\.opendj\.ldap\.spi\.LDAPListenerImpl\s*getLDAPListener\(java\.util\.Set,\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory,\s*org\.forgerock\.util\.Options\)]</to>
    <justification>Accept multiple SocketAddress to bind to</justification>
  </difference>
</differences>
opendj-core/pom.xml
@@ -50,6 +50,18 @@
        </dependency>
        <dependency>
            <groupId>org.reactivestreams</groupId>
            <artifactId>reactive-streams</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>io.reactivex.rxjava2</groupId>
            <artifactId>rxjava</artifactId>
            <version>2.0.0-RC3</version>
        </dependency>
        <dependency>
            <groupId>org.forgerock.commons</groupId>
            <artifactId>i18n-slf4j</artifactId>
        </dependency>
@@ -113,8 +125,7 @@
                            com.forgerock.opendj.ldap*,
                            org.forgerock.opendj.io,
                            org.forgerock.opendj.ldap*,
                            org.forgerock.opendj.ldif,
                            org.forgerock.opendj.security
                            org.forgerock.opendj.ldif
                        </Export-Package>
                        <Build-Maven>Apache Maven ${maven.version}</Build-Maven>
                        <SCM-Revision>${buildRevision}</SCM-Revision>
opendj-core/src/main/java/com/forgerock/reactive/Action.java
New file
@@ -0,0 +1,29 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.reactive;
/**
 * A functional interface similar to Runnable but allows throwing a checked exception.
 */
public interface Action {
    /**
     * Runs the action and optionally throws a checked exception.
     *
     * @throws Exception
     *             if the implementation wishes to throw a checked exception
     */
    void run() throws Exception;
}
opendj-core/src/main/java/com/forgerock/reactive/Completable.java
New file
@@ -0,0 +1,58 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.reactive;
import org.reactivestreams.Publisher;
/** {@link Completable} is used to communicates a terminated operation which doesn't produce a result. */
public interface Completable extends Publisher<Void> {
    /** Emitter is used to notify when the operation has been completed, successfully or not. */
    public interface Emitter {
        /** Notify that this {@link Completable} is now completed. */
        void complete();
        /**
         * Notify that this {@link Completable} cannot be completed because of an error.
         *
         * @param error
         *            The error preventing this {@link Completable} to complete
         */
        void onError(Throwable error);
    }
    /** Adapts the streaming api to a callback one. */
    public interface OnSubscribe {
        /**
         * Called when the streaming api has been subscribed.
         *
         * @param e
         *            The {@link Emitter} to use to communicate the completeness of this {@link Completable}
         */
        void onSubscribe(Emitter e);
    }
    /**
     * Creates a {@link Single} which will emit the specified value when this {@link Completable} complete.
     *
     * @param <V>
     *            Type of the value to emit
     * @param value
     *            The value to emit on completion
     * @return A new {@link Single}
     */
    <V> Single<V> toSingle(V value);
}
opendj-core/src/main/java/com/forgerock/reactive/Consumer.java
New file
@@ -0,0 +1,34 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.reactive;
/**
 * A functional interface (callback) that consumes a single value.
 *
 * @param <V>
 *            the value type
 */
public interface Consumer<V> {
    /**
     * Consume the given value.
     *
     * @param item
     *            the value
     * @throws Exception
     *             on error
     */
    void accept(V item) throws Exception;
}
opendj-core/src/main/java/com/forgerock/reactive/ReactiveFilter.java
New file
@@ -0,0 +1,185 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.reactive;
import org.forgerock.util.Function;
import org.forgerock.util.promise.NeverThrowsException;
/**
 * Filters and/or transforms the request and/or response of an exchange.
 *
 *                +--------+              +---------+
 *   ---> I1 ---> |        | ---> I2 ---> |         |
 *                | Filter |              | Handler |
 *   <--- O1 --<  |        | <--- O2 <--- |         |
 *                +--------+              +---------+
 *
 *                +--------+              +---------+              +---------+
 *   ---> I1 ---> |  One   | ---> I2 ---> | AndThen | ---> I3 ---> |         |
 *                | Filter |              | Filter  |              | Handler |
 *   <--- O1 --<  |        | <--- O2 <--- |         | <--- O3 ---< |         |
 *                +--------+              +---------+              +---------+
 *
 * @param <C>
 *            Context in which the filter will be evaluated
 * @param <I1>
 *            Type of the request received by this filter
 * @param <O1>
 *            Type of the response answered by this filter
 * @param <I2>
 *            Type of the request emitted to the handler/next filter
 * @param <O2>
 *            Type of the response emitted by the handler/next filter
 */
public abstract class ReactiveFilter<C, I1, O1, I2, O2> {
    /**
     * A simple {@link ReactiveHandler} performing simple filtering without type transformation.
     *
     * @param <C>
     *            Context in which the filter will be evaluated
     * @param <I>
     *            Type of the filtered request
     * @param <O>
     *            Type of the filtered response
     */
    public static abstract class SimpleReactiveFilter<C, I, O> extends ReactiveFilter<C, I, O, I, O> {
    }
    /**
     * Optionally concatenate a new filter to this filter.
     *
     * @param condition
     *            If true, next will be concatenated to this filter
     * @param next
     *            The filter to optionally concatenate with this one
     * @return If condition is true, returns the concatenation of this filter and next. Otherwise, return this filter.
     */
    public ReactiveFilter<C, I1, O1, I2, O2> andThen(final boolean condition,
            final ReactiveFilter<C, I2, O2, I2, O2> next) {
        if (!condition) {
            return ReactiveFilter.this;
        }
        return new ConcatenatedFilter<C, I1, O1, I2, O2>(
                new Function<ReactiveHandler<C, I2, O2>, ReactiveHandler<C, I1, O1>, NeverThrowsException>() {
                    @Override
                    public ReactiveHandler<C, I1, O1> apply(ReactiveHandler<C, I2, O2> handler) {
                        return andThen(next.andThen(handler));
                    }
                });
    }
    /**
     * Concatenate a transformer filter to this filter.
     *
     * @param <I3>
     *            Target type of the request transformed by the filter
     * @param <O3>
     *            Source type of the response transformed by the filter
     * @param next
     *            The transformer filter to add after this filter.
     * @return A new {@link ReactiveFilter} results of the concatenation of this filter and the transformer filter
     */
    public <I3, O3> ReactiveFilter<C, I1, O1, I3, O3> andThen(final ReactiveFilter<C, I2, O2, I3, O3> next) {
        return new ConcatenatedFilter<C, I1, O1, I3, O3>(
                new Function<ReactiveHandler<C, I3, O3>, ReactiveHandler<C, I1, O1>, NeverThrowsException>() {
                    @Override
                    public ReactiveHandler<C, I1, O1> apply(ReactiveHandler<C, I3, O3> handler) {
                        return andThen(next.andThen(handler));
                    }
                });
    }
    /**
     * Add a new filtering step after this one.
     *
     * @param next
     *            The filter to add after this one
     * @return A new {@link ReactiveFilter} result of the concatenation of this filter and next
     */
    public ReactiveFilter<C, I1, O1, I2, O2> andThen(final SimpleReactiveFilter<C, I2, O2> next) {
        return new ConcatenatedFilter<C, I1, O1, I2, O2>(
                new Function<ReactiveHandler<C, I2, O2>, ReactiveHandler<C, I1, O1>, NeverThrowsException>() {
                    @Override
                    public ReactiveHandler<C, I1, O1> apply(ReactiveHandler<C, I2, O2> handler) {
                        return andThen(next.andThen(handler));
                    }
                });
    }
    /**
     * Terminate the filter chain with the specified handler.
     *
     * @param handler
     *            {@link ReactiveHandler} in charge of processing the request
     * @return A new {@link ReactiveHandler} results of the concatenation of this filter and handler
     */
    public ReactiveHandler<C, I1, O1> andThen(final ReactiveHandler<C, I2, O2> handler) {
        final ReactiveFilter<C, I1, O1, I2, O2> parent = this;
        return new ReactiveHandler<C, I1, O1>() {
            @Override
            public Single<O1> handle(final C context, final I1 request) throws Exception {
                return parent.filter(context, request, handler);
            }
        };
    }
    /**
     * Perform the request and/or response filtering and/or transformation.
     *
     * @param context
     *            Context of the filtering/transformation
     * @param request
     *            Request to filter
     * @param next
     *            {@link ReactiveHandler} to call once the filtering/transformation is done
     * @return The filtered and/or transformed response
     * @throws Exception
     *             If the operation cannot be done
     */
    public abstract Single<O1> filter(final C context, final I1 request, final ReactiveHandler<C, I2, O2> next)
            throws Exception;
    private static final class ConcatenatedFilter<C, I1, O1, I2, O2> extends ReactiveFilter<C, I1, O1, I2, O2> {
        private final Function<ReactiveHandler<C, I2, O2>, ReactiveHandler<C, I1, O1>, NeverThrowsException> converter;
        ConcatenatedFilter(
                Function<ReactiveHandler<C, I2, O2>, ReactiveHandler<C, I1, O1>, NeverThrowsException> converter) {
            this.converter = converter;
        }
        @Override
        public <I3, O3> ReactiveFilter<C, I1, O1, I3, O3> andThen(final ReactiveFilter<C, I2, O2, I3, O3> nextFilter) {
            return new ConcatenatedFilter<C, I1, O1, I3, O3>(
                    new Function<ReactiveHandler<C, I3, O3>, ReactiveHandler<C, I1, O1>, NeverThrowsException>() {
                        @Override
                        public ReactiveHandler<C, I1, O1> apply(ReactiveHandler<C, I3, O3> handler) {
                            return converter.apply(nextFilter.andThen(handler));
                        }
                    });
        }
        @Override
        public ReactiveHandler<C, I1, O1> andThen(ReactiveHandler<C, I2, O2> handler) {
            return converter.apply(handler);
        }
        @Override
        public Single<O1> filter(C context, I1 request, ReactiveHandler<C, I2, O2> handler) throws Exception {
            return converter.apply(handler).handle(context, request);
        }
    }
}
opendj-core/src/main/java/com/forgerock/reactive/ReactiveHandler.java
New file
@@ -0,0 +1,41 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.reactive;
/**
 * Handle the processing of a request in a given context and return the resulting response.
 *
 * @param <CTX>
 *            Type of the context
 * @param <REQ>
 *            Type of the request to process
 * @param <REP>
 *            Type of the response
 */
public interface ReactiveHandler<CTX, REQ, REP> {
    /**
     * Process the request given the context.
     *
     * @param context
     *            Context in which the request must be processed
     * @param request
     *            The response to process
     * @return A {@link Single} response.
     * @throws Exception
     *             if the request cannot be processed
     */
    Single<REP> handle(final CTX context, final REQ request) throws Exception;
}
opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
New file
@@ -0,0 +1,310 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.reactive;
import org.forgerock.util.Function;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import io.reactivex.CompletableEmitter;
import io.reactivex.CompletableOnSubscribe;
import io.reactivex.Flowable;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.SingleSource;
/**
 * {@link Stream} and {@link Single} implementations based on RxJava.
 */
public final class RxJavaStreams {
    private RxJavaStreams() {
        // Hide
    }
    /**
     * Create a new {@link Stream} from the given {@link Publisher}.
     *
     * @param <V>
     *            Type of data emitted
     * @param publisher
     *            The {@link Publisher} to convert
     * @return A new {@link Stream}
     */
    public static <V> Stream<V> streamFromPublisher(final Publisher<V> publisher) {
        return new RxJavaStream<>(Flowable.fromPublisher(publisher));
    }
    /**
     * Create a new {@link Stream} composed only of the given value.
     *
     * @param <V>
     *            Type of data emitted
     * @param value
     *            The value emitted by this stream
     * @return A new {@link Stream}
     */
    public static <V> Stream<V> streamFrom(final V value) {
        return new RxJavaStream<>(Flowable.just(value));
    }
    /**
     * Create a new {@link Stream} composed only of the given error.
     *
     * @param <V>
     *            Type of data emitted
     * @param error
     *            The error emitted by this stream
     * @return A new {@link Stream}
     */
    public static <V> Stream<V> streamError(final Throwable error) {
        return new RxJavaStream<>(Flowable.<V> error(error));
    }
    /**
     * Create a new empty {@link Stream}.
     *
     * @param <V>
     *            Type of data emitted
     * @return An empty {@link Stream}
     */
    public static <V> Stream<V> emptyStream() {
        return new RxJavaStream<>(Flowable.<V> empty());
    }
    /**
     * Create a new {@link Single} from the given {@link Publisher}.
     *
     * @param <V>
     *            Type of the datum emitted
     * @param publisher
     *            The {@link Publisher} to convert
     * @return A new {@link Stream}
     */
    public static <V> Single<V> singleFromPublisher(final Publisher<V> publisher) {
        return new RxJavaSingle<>(io.reactivex.Single.fromPublisher(publisher));
    }
    /**
     * Create a new {@link Single} from the given value.
     *
     * @param <V>
     *            Type of the datum emitted
     * @param value
     *            The value contained by this {@link Single}
     * @return A new {@link Single}
     */
    public static <V> Single<V> singleFrom(final V value) {
        return new RxJavaSingle<>(io.reactivex.Single.just(value));
    }
    /**
     * Creates a bridge from callback world to {@link Single}.
     *
     * @param <V>
     *            Type of the datum emitted
     * @param onSubscribe
     *            Action to perform once this {@link Single} has been subscribed to.
     * @return A new {@link Single}
     */
    public static <V> Single<V> newSingle(final Single.OnSubscribe<V> onSubscribe) {
        return new RxJavaSingle<>(io.reactivex.Single.create(new SingleOnSubscribe<V>() {
            @Override
            public void subscribe(final SingleEmitter<V> e) throws Exception {
                onSubscribe.onSubscribe(new Single.Emitter<V>() {
                    @Override
                    public void onSuccess(V value) {
                        e.onSuccess(value);
                    }
                    @Override
                    public void onError(Throwable error) {
                        e.onError(error);
                    }
                });
            }
        }));
    }
    /**
     * Creates a bridge from callback world to {@link Completable}.
     *
     * @param onSubscribe
     *            Action to perform once this {@link Completable} has been subscribed to.
     * @return A new {@link Completable}
     */
    public static Completable newCompletable(final Completable.OnSubscribe onSubscribe) {
        return new RxJavaCompletable(io.reactivex.Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(final CompletableEmitter e) throws Exception {
                onSubscribe.onSubscribe(new Completable.Emitter() {
                    @Override
                    public void complete() {
                        e.onComplete();
                    }
                    @Override
                    public void onError(Throwable t) {
                        e.onError(t);
                    }
                });
            }
        }));
    }
    private static final class RxJavaStream<V> implements Stream<V> {
        private Flowable<V> impl;
        private RxJavaStream(final Flowable<V> impl) {
            this.impl = impl;
        }
        @Override
        public void subscribe(Subscriber<? super V> s) {
            impl.subscribe(s);
        }
        @Override
        public <O> Stream<O> map(final Function<V, O, Exception> function) {
            return new RxJavaStream<>(impl.map(new io.reactivex.functions.Function<V, O>() {
                @Override
                public O apply(V t) throws Exception {
                    return function.apply(t);
                }
            }));
        }
        @Override
        public <O> Stream<O> flatMap(final Function<? super V, ? extends Publisher<? extends O>, Exception> function,
                int maxConcurrency) {
            return new RxJavaStream<>(impl.flatMap(new io.reactivex.functions.Function<V, Publisher<? extends O>>() {
                @Override
                public Publisher<? extends O> apply(V t) throws Exception {
                    return function.apply(t);
                }
            }, maxConcurrency));
        }
        @Override
        public void subscribe(final Consumer<V> onResult, final Consumer<Throwable> onError, final Action onComplete) {
            impl.subscribe(new io.reactivex.functions.Consumer<V>() {
                @Override
                public void accept(V t) throws Exception {
                    onResult.accept(t);
                }
            }, new io.reactivex.functions.Consumer<Throwable>() {
                @Override
                public void accept(Throwable t) throws Exception {
                    onError.accept(t);
                }
            }, new io.reactivex.functions.Action() {
                @Override
                public void run() throws Exception {
                    onComplete.run();
                }
            });
        }
        @Override
        public Stream<V> onErrorResumeWith(final Function<Throwable, Publisher<V>, Exception> function) {
            return new RxJavaStream<>(
                    impl.onErrorResumeNext(new io.reactivex.functions.Function<Throwable, Publisher<? extends V>>() {
                        @Override
                        public Publisher<? extends V> apply(Throwable t) throws Exception {
                            return function.apply(t);
                        }
                    }));
        }
        @Override
        public void subscribe() {
            impl.subscribe();
        }
    }
    private static final class RxJavaSingle<V> implements Single<V> {
        private final io.reactivex.Single<V> impl;
        private RxJavaSingle(io.reactivex.Single<V> impl) {
            this.impl = impl;
        }
        @Override
        public Stream<V> toStream() {
            return new RxJavaStream<>(impl.toFlowable());
        }
        @Override
        public <O> Single<O> map(final Function<V, O, Exception> function) {
            return new RxJavaSingle<>(impl.map(new io.reactivex.functions.Function<V, O>() {
                @Override
                public O apply(V t) throws Exception {
                    return function.apply(t);
                }
            }));
        }
        @Override
        public void subscribe(Subscriber<? super V> s) {
            impl.toFlowable().subscribe(s);
        }
        @Override
        public void subscribe(final Consumer<? super V> resultConsumer, final Consumer<Throwable> errorConsumer) {
            impl.subscribe(new io.reactivex.functions.Consumer<V>() {
                @Override
                public void accept(V t) throws Exception {
                    resultConsumer.accept(t);
                }
            }, new io.reactivex.functions.Consumer<Throwable>() {
                @Override
                public void accept(Throwable t) throws Exception {
                    errorConsumer.accept(t);
                }
            });
        }
        @Override
        public <O> Single<O> flatMap(final Function<V, Publisher<O>, Exception> function) {
            return new RxJavaSingle<>(impl.flatMap(new io.reactivex.functions.Function<V, SingleSource<O>>() {
                @Override
                public SingleSource<O> apply(V t) throws Exception {
                    return io.reactivex.Single.fromPublisher(function.apply(t));
                }
            }));
        }
    }
    private static final class RxJavaCompletable implements Completable {
        private final io.reactivex.Completable impl;
        RxJavaCompletable(io.reactivex.Completable impl) {
            this.impl = impl;
        }
        @Override
        public <V> Single<V> toSingle(V value) {
            return new RxJavaSingle<>(impl.toSingleDefault(value));
        }
        @Override
        public void subscribe(Subscriber<? super Void> s) {
            impl.<Void>toFlowable().subscribe(s);
        }
    }
}
opendj-core/src/main/java/com/forgerock/reactive/Single.java
New file
@@ -0,0 +1,96 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.reactive;
import org.forgerock.util.Function;
import org.reactivestreams.Publisher;
/**
 * Single is a reactive-streams compatible promise.
 *
 * @param <V>
 *            Type of the datum emitted
 */
public interface Single<V> extends Publisher<V> {
    /** Emitter is used to notify when the operation has been completed, successfully or not. */
    public interface Emitter<V> {
        /**
         * Signal a success value.
         *
         * @param t
         *            the value, not null
         */
        void onSuccess(V t);
        /**
         * Signal an exception.
         *
         * @param t
         *            the exception, not null
         */
        void onError(Throwable t);
    }
    /** Adapts the streaming api to a callback one. */
    public interface OnSubscribe<V> {
        /**
         * Called for each SingleObserver that subscribes.
         * @param e the safe emitter instance, never null
         * @throws Exception on error
         */
        void onSubscribe(Emitter<V> e) throws Exception;
    }
    /**
     * Transform this {@link Single} into a {@link Stream}.
     *
     * @return A new {@link Stream}
     */
    Stream<V> toStream();
    /**
     * Transforms the value emitted by this single.
     *
     * @param <O>
     *            Type of the datum emitted after transformation
     * @param function
     *            Function to apply to the result of this single.
     * @return A new {@link Single} with the transformed value.
     */
    <O> Single<O> map(Function<V, O, Exception> function);
    /**
     * Transforms asynchronously the value emitted by this single into another.
     *
     * @param <O>
     *            Type of the datum emitted after transformation
     * @param function
     *            Function to apply to perform the asynchronous transformation
     * @return A new {@link Single} transforming the datum emitted by this {@link Single}.
     */
    <O> Single<O> flatMap(Function<V, Publisher<O>, Exception> function);
    /**
     * Subscribe to the single value emitted by this {@link Single}.
     *
     * @param resultConsumer
     *            A {@link Consumer} which will be invoked by the value emitted by this single
     * @param errorConsumer
     *            A {@link Consumer} which will be invoked with the error emitted by this single
     */
    void subscribe(Consumer<? super V> resultConsumer, Consumer<Throwable> errorConsumer);
}
opendj-core/src/main/java/com/forgerock/reactive/Stream.java
New file
@@ -0,0 +1,81 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.reactive;
import org.forgerock.util.Function;
import org.reactivestreams.Publisher;
/**
 * Stream is a reactive-streams compliant way to chain operations and transformation on a stream of data.
 *
 * @param <V>
 *            Type of data emitted
 */
public interface Stream<V> extends Publisher<V> {
    /**
     * Transform the data emitted by this stream.
     *
     * @param <O>
     *            Type of data emitted after transformation
     * @param function
     *            The function to apply to each data of this stream
     * @return a new {@link Stream} emitting transformed data
     */
    <O> Stream<O> map(Function<V, O, Exception> function);
    /**
     * Transform each data emitted by this stream into a new stream of data. All these streams are then merged together.
     *
     * @param <O>
     *            Type of data emitted after transformation
     * @param function
     *            The function to transform each data into a new stream
     * @param maxConcurrency
     *            Maximum number of output stream which can be merged. Once this number is reached, the {@link Stream}
     *            will stop requesting data from this stream.
     * @return A new {@link Stream} performing the transformation
     */
    <O> Stream<O> flatMap(Function<? super V, ? extends Publisher<? extends O>, Exception> function,
            int maxConcurrency);
    /**
     * Subscribe to the data emitted by this {@link Stream}.
     *
     * @param onResult
     *            The {@link Consumer} invoked for each data of this stream
     * @param onError
     *            The {@link Consumer} invoked on error in this stream.
     * @param onComplete
     *            The {@link Action} invoked once this {@link Stream} is completed.
     */
    void subscribe(Consumer<V> onResult, Consumer<Throwable> onError, Action onComplete);
    /**
     * When an error occurs in this stream, continue the processing with the new {@link Stream} provided by the
     * function.
     *
     * @param function
     *            Generates the stream which must will used to resume operation when this {@link Stream} failed.
     * @return A new {@link Stream}
     */
    Stream<V> onErrorResumeWith(Function<Throwable, Publisher<V>, Exception> function);
    /**
     * Subscribe to this stream and drop all data produced by it.
     */
    void subscribe();
}
opendj-core/src/main/java/com/forgerock/reactive/package-info.java
New file
@@ -0,0 +1,19 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
/** Provides asynchronous stream processing with non-blocking back pressure. */
package com.forgerock.reactive;
opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
@@ -24,7 +24,7 @@
/**
 * Common options for LDAP clients and listeners.
 */
abstract class CommonLDAPOptions {
public abstract class CommonLDAPOptions {
    /**
     * Specifies the class loader which will be used to load the
     * {@link org.forgerock.opendj.ldap.spi.TransportProvider TransportProvider}.
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPClientContext.java
@@ -12,15 +12,16 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2010 Sun Microsystems, Inc.
 * Portions copyright 2012 ForgeRock AS.
 * Portions copyright 2012-2016 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.security.sasl.SaslServer;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
@@ -32,6 +33,29 @@
 */
public interface LDAPClientContext {
    /** Listens for disconnection event. */
    public interface DisconnectListener {
        /**
         * Invoked when the connection has been closed as a result of a client disconnect, a fatal connection error, or
         * a server-side {@link #disconnect}.
         *
         * @param context
         *            The {@link LDAPClientContext} which has been disconnected
         * @param resultCode
         *            The {@link ResultCode} of the notification sent, or null
         * @param message
         *            The message of the notification sent, or null
         */
        void connectionDisconnected(final LDAPClientContext context, final ResultCode resultCode, final String message);
    }
    /**
     * Register a listener which will be notified when this {@link LDAPClientContext} is disconnected.
     *
     * @param listener The {@link DisconnectListener} to register.
     */
    void onDisconnect(final DisconnectListener listener);
    /**
     * Disconnects the client without sending a disconnect notification.
     * <p>
@@ -56,7 +80,7 @@
     *            The diagnostic message, which may be empty or {@code null}
     *            indicating that none was provided.
     */
    void disconnect(ResultCode resultCode, String message);
    void disconnect(final ResultCode resultCode, final String message);
    /**
     * Returns the {@code InetSocketAddress} associated with the local system.
@@ -123,40 +147,20 @@
    void sendUnsolicitedNotification(ExtendedResult notification);
    /**
     * Installs the provided connection security layer to the underlying
     * connection. This may be used to add a SASL integrity and/or
     * confidentiality protection layer after SASL authentication has completed,
     * but could also be used to add other layers such as compression. Multiple
     * layers may be installed.
     *
     * @param layer
     *            The negotiated bind context that can be used to encode and
     *            decode data on the connection.
     */
    void enableConnectionSecurityLayer(ConnectionSecurityLayer layer);
    /**
     * Installs the TLS/SSL security layer on the underlying connection. The
     * TLS/SSL security layer will be installed beneath any existing connection
     * security layers and can only be installed at most once.
     *
     * @param sslContext
     *            The {@code SSLContext} which should be used to secure the
     * @param protocols
     *            Names of all the protocols to enable or {@code null} to use
     *            the default protocols.
     * @param suites
     *            Names of all the suites to enable or {@code null} to use the
     *            default cipher suites.
     * @param wantClientAuth
     *            Set to {@code true} if client authentication is requested, or
     *            {@code false} if no client authentication is desired.
     * @param needClientAuth
     *            Set to {@code true} if client authentication is required, or
     *            {@code false} if no client authentication is desired.
     * @throws IllegalStateException
     *             If the TLS/SSL security layer has already been installed.
     * @param sslEngine
     *            The {@code SSLEngine} which should be used to secure the conneciton.
     */
    void enableTLS(SSLContext sslContext, String[] protocols, String[] suites,
            boolean wantClientAuth, boolean needClientAuth);
    void enableTLS(SSLEngine sslEngine);
    /**
     * Installs the SASL security layer on the underlying connection.
     *
     * @param saslServer
     *            The {@code SaslServer} which should be used to secure the conneciton.
     */
    void enableSASL(SaslServer saslServer);
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
@@ -17,8 +17,8 @@
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.ldap.CoreMessages.HBCF_CONNECTION_CLOSED_BY_CLIENT;
import static com.forgerock.opendj.ldap.CoreMessages.HBCF_HEARTBEAT_FAILED;
import static com.forgerock.opendj.ldap.CoreMessages.HBCF_HEARTBEAT_TIMEOUT;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_CONNECTION_UNEXPECTED;
import static com.forgerock.opendj.ldap.CoreMessages.LDAP_CONNECTION_CONNECT_TIMEOUT;
import static com.forgerock.opendj.util.StaticUtils.DEFAULT_SCHEDULER;
import static java.util.concurrent.TimeUnit.*;
@@ -502,7 +502,7 @@
                        connectException = newHeartBeatTimeoutError();
                    } else {
                        connectException = newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
                                                            ERR_CONNECTION_UNEXPECTED.get(e),
                                                            HBCF_HEARTBEAT_FAILED.get(),
                                                            e);
                    }
                    if (promise.tryHandleException(connectException)) {
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
@@ -12,14 +12,16 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2009-2010 Sun Microsystems, Inc.
 * Portions copyright 2012-2015 ForgeRock AS.
 * Portions copyright 2012-2016 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashSet;
import java.util.Set;
import org.forgerock.opendj.ldap.spi.LDAPListenerImpl;
import org.forgerock.opendj.ldap.spi.TransportProvider;
@@ -97,6 +99,12 @@
    public static final Option<Integer> REQUEST_MAX_SIZE_IN_BYTES = Option.withDefault(5 * 1024 * 1024);
    /**
     * Specifies the maximum number of concurrent requests per connection. Once this number is reached,
     * back-pressure mechanism will stop reading requests from the connection.
     */
    public static final Option<Integer> MAX_CONCURRENT_REQUESTS = Option.withDefault(1024);
    /**
     * We implement the factory using the pimpl idiom in order have
     * cleaner Javadoc which does not expose implementation methods.
     */
@@ -145,9 +153,7 @@
    public LDAPListener(final int port,
            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
            final Options options) throws IOException {
        Reject.ifNull(factory, options);
        this.provider = getTransportProvider(options);
        this.impl = provider.getLDAPListener(new InetSocketAddress(port), factory, options);
        this(new InetSocketAddress(port), factory, options);
    }
    /**
@@ -188,12 +194,14 @@
     *             If {@code address}, {code factory}, or {@code options} was
     *             {@code null}.
     */
    public LDAPListener(final InetSocketAddress address,
    public LDAPListener(final SocketAddress address,
            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
            final Options options) throws IOException {
        Reject.ifNull(address, factory, options);
        this.provider = getTransportProvider(options);
        this.impl = provider.getLDAPListener(address, factory, options);
        final Set<SocketAddress> addresses = new HashSet<>();
        addresses.add(address);
        this.impl = provider.getLDAPListener(addresses, factory, options);
    }
    /**
@@ -241,10 +249,7 @@
    public LDAPListener(final String host, final int port,
            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
            final Options options) throws IOException {
        Reject.ifNull(host, factory, options);
        final InetSocketAddress address = new InetSocketAddress(host, port);
        this.provider = getTransportProvider(options);
        this.impl = provider.getLDAPListener(address, factory, options);
        this(new InetSocketAddress(host, port), factory, options);
    }
    /** Closes this LDAP connection listener. */
@@ -254,42 +259,12 @@
    }
    /**
     * Returns the {@code InetAddress} that this LDAP listener is listening on.
     *
     * @return The {@code InetAddress} that this LDAP listener is listening on.
     */
    public InetAddress getAddress() {
        return getSocketAddress().getAddress();
    }
    /**
     * Returns the host name that this LDAP listener is listening on. The
     * returned host name is the same host name that was provided during
     * construction and may be an IP address. More specifically, this method
     * will not perform a reverse DNS lookup.
     *
     * @return The host name that this LDAP listener is listening on.
     */
    public String getHostName() {
        return Connections.getHostString(getSocketAddress());
    }
    /**
     * Returns the port that this LDAP listener is listening on.
     *
     * @return The port that this LDAP listener is listening on.
     */
    public int getPort() {
        return getSocketAddress().getPort();
    }
    /**
     * Returns the address that this LDAP listener is listening on.
     *
     * @return The address that this LDAP listener is listening on.
     */
    public InetSocketAddress getSocketAddress() {
        return impl.getSocketAddress();
    public Set<? extends SocketAddress> getSocketAddresses() {
        return impl.getSocketAddresses();
    }
    /**
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPListenerImpl.java
@@ -11,12 +11,13 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2014 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import java.io.Closeable;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
/**
 * Interface for all classes that actually implement {@code LDAPListener}.
@@ -31,11 +32,11 @@
public interface LDAPListenerImpl extends Closeable {
    /**
     * Returns the address that this LDAP listener is listening on.
     * Returns the addresses that this LDAP listener is listening on.
     *
     * @return The address that this LDAP listener is listening on.
     * @return The addresses that this LDAP listener is listening on.
     */
    InetSocketAddress getSocketAddress();
    Set<? extends SocketAddress> getSocketAddresses();
    /**
     * Closes this stream and releases any system resources associated
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LdapMessages.java
New file
@@ -0,0 +1,157 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.schema.Schema;
/**
 * Contains statics methods to create ldap messages.
 */
public final class LdapMessages {
    private LdapMessages() {
        // Nothing to so
    }
    /**
     * Creates a new {@link LdapRawMessage} containing a partially decoded LDAP message.
     *
     * @param messageType
     *            Operation code of the message
     * @param messageId
     *            Unique identifier of this message
     * @param protocolVersion
     *            Protocol version to use (only for Bind requests)
     * @param rawDn
     *            Unparsed name contained in the request (or null if DN is not applicable)
     * @param schema
     *            Schema to use to parse the DN
     * @param reader
     *            An {@link ASN1Reader} containing the full encoded ldap message packet.
     * @return A new {@link LdapRawMessage}
     */
    public static LdapRawMessage newRawMessage(final byte messageType, final int messageId, final int protocolVersion,
            final String rawDn, final Schema schema, final ASN1Reader reader) {
        return new LdapRawMessage(messageType, messageId, protocolVersion, rawDn, schema, reader);
    }
    /**
     * Creates a new {@link LdapResponseMessage}, adding low-level ldap protocol specific informations to a
     * {@link Response}.
     *
     * @param messageType
     *            Operation code of the response
     * @param messageId
     *            Message identifier of the request owning this response.
     * @param response
     *            The response
     * @return A new {@link LdapResponseMessage}
     */
    public static LdapResponseMessage newResponseMessage(final byte messageType, final int messageId,
            final Response response) {
        return new LdapResponseMessage(messageType, messageId, response);
    }
    /**
     * Represents an encoded LDAP message with it's envelope.
     */
    public static final class LdapRawMessage extends LdapMessageEnvelope<ASN1Reader> {
        private final String rawDn;
        private final Schema schema;
        private final int version;
        private DN dn;
        private LdapRawMessage(final byte messageType, final int messageId, final int version, final String rawDn,
                final Schema schema, final ASN1Reader content) {
            super(messageType, messageId, content);
            this.version = version;
            this.rawDn = rawDn;
            this.schema = schema;
        }
        /**
         * Get the Ldap version requested by this message (Bind request only).
         *
         * @return The ldap protocol version
         */
        public int getVersion() {
            return version;
        }
        /**
         * Get the raw form of the {@link DN} contained in the message (or null if the message doesn't contains a DN).
         *
         * @return The {@link DN} contained in request, or null if the message doesn't contains a DN.
         */
        public String getRawDn() {
            return rawDn;
        }
        /**
         * Get the decoded form of the {@link DN} contained in the message (or null if the message doesn't contains a
         * DN).
         *
         * @return The decoded {@link DN} contained in the request, or null if the message doesn't contains a DN.
         */
        public DN getDn() {
            if (rawDn == null) {
                return null;
            }
            if (dn != null) {
                return dn;
            }
            dn = DN.valueOf(rawDn.toString(), schema);
            return dn;
        }
    }
    /**
     * Represents a {@link Response} and its envelope.
     */
    public static final class LdapResponseMessage extends LdapMessageEnvelope<Response> {
        private LdapResponseMessage(final byte messageType, final int messageId, final Response content) {
            super(messageType, messageId, content);
        }
    }
    private static abstract class LdapMessageEnvelope<T> {
        private final T content;
        private final int messageId;
        private final byte messageType;
        public LdapMessageEnvelope(final byte messageType, final int messageId, final T content) {
            this.messageType = messageType;
            this.messageId = messageId;
            this.content = content;
        }
        public byte getMessageType() {
            return messageType;
        }
        public int getMessageId() {
            return messageId;
        }
        public T getContent() {
            return content;
        }
    }
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/TransportProvider.java
@@ -11,12 +11,13 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2015 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
@@ -51,8 +52,8 @@
  /**
     * Returns an implementation of {@code LDAPListener}.
     *
     * @param address
     *            The address to listen on.
     * @param addresses
     *            The addresses to listen on.
     * @param factory
     *            The server connection factory which will be used to create
     *            server connections.
@@ -63,8 +64,7 @@
     *             If an error occurred while trying to listen on the provided
     *             address.
     */
    LDAPListenerImpl getLDAPListener(InetSocketAddress address,
    LDAPListenerImpl getLDAPListener(Set<? extends SocketAddress> addresses,
            ServerConnectionFactory<LDAPClientContext, Integer> factory, Options options)
            throws IOException;
}
opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java
@@ -11,7 +11,7 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2015 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package org.forgerock.opendj.io;
@@ -487,8 +487,7 @@
            throws Exception {
        LDAPWriter<? extends ASN1Writer> writer = getLDAPWriter();
        writing.perform(writer);
        LDAPReader<? extends ASN1Reader> reader = getLDAPReader();
        transferFromWriterToReader(writer, reader);
        LDAPReader<? extends ASN1Reader> reader = getLDAPReader(writer);
        reader.readMessage(messageHandler);
    }
@@ -503,8 +502,7 @@
    protected abstract LDAPReader<? extends ASN1Reader> getLDAPReader();
    /**
     * Transfer raw data from writer to the reader.
     * Create a reader reading data contained in the writer.
     */
    protected abstract void transferFromWriterToReader(LDAPWriter<? extends ASN1Writer> writer,
            LDAPReader<? extends ASN1Reader> reader);
    protected abstract LDAPReader<? extends ASN1Reader> getLDAPReader(LDAPWriter<? extends ASN1Writer> writer);
}
opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
@@ -17,6 +17,10 @@
package org.forgerock.opendj.ldap;
import static org.forgerock.opendj.ldap.LDAPListener.CONNECT_MAX_BACKLOG;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
@@ -27,6 +31,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
@@ -62,14 +67,10 @@
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.util.Options;
import com.forgerock.opendj.ldap.controls.AccountUsabilityRequestControl;
import com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl;
import org.forgerock.util.Options;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.opendj.ldap.LDAPListener.*;
/**
 * A simple ldap server that manages 1000 entries and used for running
@@ -225,7 +226,9 @@
                        props.put(Sasl.QOP, "auth-conf,auth-int,auth");
                        saslServer =
                                Sasl.createSaslServer(saslMech, "ldap",
                                        listener.getHostName(), props,
                                       ((InetSocketAddress) listener.getSocketAddresses().iterator().next())
                                       .getHostName(),
                                       props,
                                        new CallbackHandler() {
                                            @Override
                                            public void handle(Callback[] callbacks)
@@ -264,38 +267,7 @@
                        String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP);
                        if ("auth-int".equalsIgnoreCase(qop) || "auth-conf".equalsIgnoreCase(qop)) {
                            ConnectionSecurityLayer csl = new ConnectionSecurityLayer() {
                                @Override
                                public void dispose() {
                                    try {
                                        saslServer.dispose();
                                    } catch (SaslException e) {
                                        e.printStackTrace();
                                    }
                                }
                                @Override
                                public byte[] unwrap(byte[] incoming, int offset, int len) throws LdapException {
                                    try {
                                        return saslServer.unwrap(incoming, offset, len);
                                    } catch (SaslException e) {
                                        throw newLdapException(
                                                Responses.newResult(ResultCode.OPERATIONS_ERROR).setCause(e));
                                    }
                                }
                                @Override
                                public byte[] wrap(byte[] outgoing, int offset, int len) throws LdapException {
                                    try {
                                        return saslServer.wrap(outgoing, offset, len);
                                    } catch (SaslException e) {
                                        throw newLdapException(
                                                Responses.newResult(ResultCode.OPERATIONS_ERROR).setCause(e));
                                    }
                                }
                            };
                            clientContext.enableConnectionSecurityLayer(csl);
                            clientContext.enableSASL(saslServer);
                        }
                    } else {
@@ -423,8 +395,11 @@
            if (request.getOID().equals(StartTLSExtendedRequest.OID)) {
                final R result = request.getResultDecoder().newExtendedErrorResult(ResultCode.SUCCESS, "", "");
                resultHandler.handleResult(result);
                clientContext.enableTLS(sslContext, null, sslContext.getSocketFactory()
                        .getSupportedCipherSuites(), false, false);
                final SSLEngine engine = sslContext.createSSLEngine();
                engine.setEnabledCipherSuites(sslContext.getServerSocketFactory().getSupportedCipherSuites());
                engine.setNeedClientAuth(false);
                engine.setUseClientMode(false);
                clientContext.enableTLS(engine);
            }
        }
@@ -568,6 +543,6 @@
        if (!isRunning) {
            throw new IllegalStateException("Server is not running");
        }
        return listener.getSocketAddress();
        return (InetSocketAddress) listener.getSocketAddresses().iterator().next();
    }
}
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPListener.java
@@ -11,13 +11,14 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2015 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
@@ -28,7 +29,7 @@
 */
public final class BasicLDAPListener implements LDAPListenerImpl {
    private final ServerConnectionFactory<LDAPClientContext, Integer> connectionFactory;
    private final InetSocketAddress socketAddress;
    private final Set<? extends SocketAddress> socketAddresses;
    /**
     * Creates a new LDAP listener implementation which does nothing.
@@ -43,11 +44,11 @@
     * @throws IOException
     *             is never thrown with this do-nothing implementation
     */
    public BasicLDAPListener(final InetSocketAddress address,
    public BasicLDAPListener(final Set<? extends SocketAddress> addresses,
            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
            final Options options) throws IOException {
        this.connectionFactory = factory;
        this.socketAddress = address;
        this.socketAddresses = addresses;
    }
    @Override
@@ -56,15 +57,15 @@
    }
    @Override
    public InetSocketAddress getSocketAddress() {
        return socketAddress;
    public Set<? extends SocketAddress> getSocketAddresses() {
        return socketAddresses;
    }
    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("LDAPListener(");
        builder.append(getSocketAddress());
        builder.append(getSocketAddresses());
        builder.append(')');
        return builder.toString();
    }
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicTransportProvider.java
@@ -16,7 +16,8 @@
package org.forgerock.opendj.ldap.spi;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
@@ -43,10 +44,9 @@
    }
    @Override
    public LDAPListenerImpl getLDAPListener(
            InetSocketAddress address, ServerConnectionFactory<LDAPClientContext, Integer> factory, Options options)
            throws IOException {
        return new BasicLDAPListener(address, factory, options);
    public LDAPListenerImpl getLDAPListener(Set<? extends SocketAddress> addresses,
            ServerConnectionFactory<LDAPClientContext, Integer> factory, Options options) throws IOException {
        return new BasicLDAPListener(addresses, factory, options);
    }
    @Override
opendj-grizzly/pom.xml
@@ -69,6 +69,10 @@
            <artifactId>forgerock-build-tools</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.forgerock.http</groupId>
          <artifactId>chf-http-core</artifactId>
        </dependency>
    </dependencies>
opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyTransportProvider.java
@@ -11,26 +11,62 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2014 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package com.forgerock.opendj.grizzly;
import static com.forgerock.reactive.RxJavaStreams.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
import org.forgerock.opendj.grizzly.GrizzlyLDAPConnectionFactory;
import org.forgerock.opendj.grizzly.GrizzlyLDAPListener;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.AbstractLDAPMessageHandler;
import org.forgerock.opendj.io.LDAP;
import org.forgerock.opendj.io.LDAPReader;
import org.forgerock.opendj.ldap.CommonLDAPOptions;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPClientContext.DisconnectListener;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ServerConnection;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.CompareRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
import org.forgerock.opendj.ldap.requests.GenericBindRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.requests.UnbindRequest;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.spi.LDAPConnectionFactoryImpl;
import org.forgerock.opendj.ldap.spi.LDAPListenerImpl;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage;
import org.forgerock.opendj.ldap.spi.TransportProvider;
import org.forgerock.util.Function;
import org.forgerock.util.Options;
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.Single;
import com.forgerock.reactive.Stream;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableEmitter.BackpressureMode;
import io.reactivex.FlowableOnSubscribe;
/**
 * Grizzly transport provider implementation.
 */
public class GrizzlyTransportProvider implements TransportProvider {
public final class GrizzlyTransportProvider implements TransportProvider {
    @Override
    public LDAPConnectionFactoryImpl getLDAPConnectionFactory(String host, int port, Options options) {
@@ -38,10 +74,19 @@
    }
    @Override
    public LDAPListenerImpl getLDAPListener(InetSocketAddress address,
            ServerConnectionFactory<LDAPClientContext, Integer> factory, Options options)
    public LDAPListenerImpl getLDAPListener(final Set<? extends SocketAddress> addresses,
            final ServerConnectionFactory<LDAPClientContext, Integer> factory, final Options options)
            throws IOException {
        return new GrizzlyLDAPListener(address, factory, options);
        return new GrizzlyLDAPListener(addresses, options,
                new Function<LDAPClientContext,
                             ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>,
                             LdapException>() {
                    @Override
                    public ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>> apply(
                            final LDAPClientContext clientContext) throws LdapException {
                        return newHandler(clientContext, factory, options);
                    }
                });
    }
    @Override
@@ -49,4 +94,91 @@
        return "Grizzly";
    }
    private ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>> newHandler(
            final LDAPClientContext clientContext, final ServerConnectionFactory<LDAPClientContext, Integer> factory,
            final Options options) throws LdapException {
        final ServerConnection<Integer> serverConnection = factory.handleAccept(clientContext);
        final ServerConnectionAdaptor<Integer> adapter = new ServerConnectionAdaptor<>(serverConnection);
        clientContext.onDisconnect(new DisconnectListener() {
            @Override
            public void connectionDisconnected(LDAPClientContext context, ResultCode resultCode, String message) {
                serverConnection.handleConnectionDisconnected(resultCode, message);
            }
        });
        return new ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>() {
            @Override
            public Single<Stream<Response>> handle(final LDAPClientContext context,
                    final LdapRawMessage rawRequest) throws Exception {
                final LDAPReader<ASN1Reader> reader = LDAP.getReader(rawRequest.getContent(),
                        options.get(CommonLDAPOptions.LDAP_DECODE_OPTIONS));
                return singleFrom(streamFromPublisher(Flowable.create(new FlowableOnSubscribe<Response>() {
                    @Override
                    public void subscribe(final FlowableEmitter<Response> emitter) throws Exception {
                        reader.readMessage(new AbstractLDAPMessageHandler() {
                            @Override
                            public void abandonRequest(int messageID, AbandonRequest request)
                                    throws DecodeException, IOException {
                                adapter.handleAbandon(messageID, request, emitter);
                            }
                            @Override
                            public void addRequest(int messageID, AddRequest request)
                                    throws DecodeException, IOException {
                                adapter.handleAdd(messageID, request, emitter);
                            }
                            @Override
                            public void deleteRequest(final int messageID, final DeleteRequest request)
                                    throws DecodeException, IOException {
                                adapter.handleDelete(messageID, request, emitter);
                            }
                            @Override
                            public void bindRequest(int messageID, int version, GenericBindRequest request)
                                    throws DecodeException, IOException {
                                adapter.handleBind(messageID, version, request, emitter);
                            }
                            @Override
                            public void compareRequest(int messageID, CompareRequest request)
                                    throws DecodeException, IOException {
                                adapter.handleCompare(messageID, request, emitter);
                            }
                            @Override
                            public <R extends ExtendedResult> void extendedRequest(int messageID,
                                    ExtendedRequest<R> request) throws DecodeException, IOException {
                                adapter.handleExtendedRequest(messageID, request, emitter);
                            }
                            @Override
                            public void modifyDNRequest(int messageID, ModifyDNRequest request)
                                    throws DecodeException, IOException {
                                adapter.handleModifyDN(messageID, request, emitter);
                            }
                            @Override
                            public void modifyRequest(int messageID, ModifyRequest request)
                                    throws DecodeException, IOException {
                                adapter.handleModify(messageID, request, emitter);
                            }
                            @Override
                            public void searchRequest(int messageID, SearchRequest request)
                                    throws DecodeException, IOException {
                                adapter.handleSearch(messageID, request, emitter);
                            }
                            @Override
                            public void unbindRequest(int messageID, UnbindRequest request)
                                    throws DecodeException, IOException {
                                serverConnection.handleConnectionClosed(messageID, request);
                            }
                        });
                        emitter.onComplete();
                    }
                }, BackpressureMode.ERROR)));
            }
        };
    }
}
opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ServerConnectionAdaptor.java
New file
@@ -0,0 +1,154 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package com.forgerock.opendj.grizzly;
import static org.forgerock.util.Reject.checkNotNull;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.ServerConnection;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.CompareRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.IntermediateResponse;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
import org.forgerock.util.promise.RuntimeExceptionHandler;
import io.reactivex.FlowableEmitter;
final class ServerConnectionAdaptor<C> {
    private final ServerConnection<C> adaptee;
    public ServerConnectionAdaptor(final ServerConnection<C> handler) {
        this.adaptee = checkNotNull(handler, "handler must not be null");
    }
    public void handleAdd(final C requestContext, final AddRequest request,
            final FlowableEmitter<Response> response) {
        final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response);
        adaptee.handleAdd(requestContext, request, resultAdapter, resultAdapter);
    }
    public void handleBind(final C requestContext, final int version, final BindRequest request,
            final FlowableEmitter<Response> response) {
        final ResultHandlerAdaptor<BindResult> resultAdapter = new ResultHandlerAdaptor<>(response);
        adaptee.handleBind(requestContext, version, request, resultAdapter, resultAdapter);
    }
    public void handleCompare(final C requestContext, final CompareRequest request,
            final FlowableEmitter<Response> response) {
        final ResultHandlerAdaptor<CompareResult> resultAdapter = new ResultHandlerAdaptor<>(response);
        adaptee.handleCompare(requestContext, request, resultAdapter, resultAdapter);
    }
    public void handleDelete(final C requestContext, final DeleteRequest request,
            final FlowableEmitter<Response> response) {
        final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response);
        adaptee.handleDelete(requestContext, request, resultAdapter, resultAdapter);
    }
    public <R extends ExtendedResult> void handleExtendedRequest(final C requestContext,
            final ExtendedRequest<R> request, final FlowableEmitter<Response> response) {
        final ResultHandlerAdaptor<R> resultAdapter = new ResultHandlerAdaptor<>(response);
        adaptee.handleExtendedRequest(requestContext, request, resultAdapter, resultAdapter);
    }
    public void handleModify(final C requestContext, final ModifyRequest request,
            final FlowableEmitter<Response> response) {
        final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response);
        adaptee.handleModify(requestContext, request, resultAdapter, resultAdapter);
    }
    public void handleModifyDN(final C requestContext, final ModifyDNRequest request,
            final FlowableEmitter<Response> response) {
        final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response);
        adaptee.handleModifyDN(requestContext, request, resultAdapter, resultAdapter);
    }
    public void handleSearch(final C requestContext, final SearchRequest request,
            final FlowableEmitter<Response> response) {
        final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response);
        adaptee.handleSearch(requestContext, request, resultAdapter, resultAdapter, resultAdapter);
    }
    public void handleAbandon(C requestContext, final AbandonRequest request, final FlowableEmitter<Response> out) {
        adaptee.handleAbandon(requestContext, request);
    }
    /**
     * Forward all response received from handler to a {@link LdapResponse}.
     */
    private static final class ResultHandlerAdaptor<R extends Response>
            implements IntermediateResponseHandler, SearchResultHandler, LdapResultHandler<R>, RuntimeExceptionHandler {
        private final FlowableEmitter<Response> adaptee;
        ResultHandlerAdaptor(final FlowableEmitter<Response> emitter) {
            this.adaptee = emitter;
        }
        @Override
        public boolean handleEntry(final SearchResultEntry entry) {
            adaptee.onNext(entry);
            return true;
        }
        @Override
        public boolean handleReference(final SearchResultReference reference) {
            adaptee.onNext(reference);
            return true;
        }
        @Override
        public boolean handleIntermediateResponse(final IntermediateResponse intermediateResponse) {
            adaptee.onNext(intermediateResponse);
            return true;
        }
        @Override
        public void handleResult(R result) {
            if (result != null) {
                adaptee.onNext(result);
            }
            adaptee.onComplete();
        }
        @Override
        public void handleRuntimeException(RuntimeException exception) {
            adaptee.onError(exception);
        }
        @Override
        public void handleException(LdapException exception) {
            adaptee.onError(exception);
        }
    }
}
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferReader.java
@@ -19,6 +19,7 @@
import static com.forgerock.opendj.ldap.CoreMessages.*;
import java.io.IOException;
import java.nio.charset.Charset;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -29,9 +30,6 @@
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.DecodeException;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.memory.BuffersBuffer;
import org.glassfish.grizzly.memory.CompositeBuffer;
import org.glassfish.grizzly.memory.MemoryManager;
/** Grizzly ASN1 reader implementation. */
final class ASN1BufferReader extends AbstractASN1Reader {
@@ -127,15 +125,16 @@
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
    private static final int MAX_STRING_BUFFER_SIZE = 1024;
    private int markState;
    private SequenceLimiter markReadLimiter;
    private int state = ASN1.ELEMENT_READ_STATE_NEED_TYPE;
    private byte peekType;
    private int peekLength = -1;
    private int lengthBytesNeeded;
    private final int maxElementSize;
    private final CompositeBuffer buffer;
    private final Buffer buffer;
    private SequenceLimiter readLimiter;
    private final byte[] stringBuffer;
    /**
     * Creates a new ASN1 reader whose source is the provided input stream and
@@ -147,11 +146,10 @@
     * @param memoryManager
     *            The memory manager to use for buffering.
     */
    ASN1BufferReader(final int maxElementSize, final MemoryManager<?> memoryManager) {
    ASN1BufferReader(final int maxElementSize, final Buffer buffer) {
        this.readLimiter = new RootSequenceLimiter();
        this.stringBuffer = new byte[MAX_STRING_BUFFER_SIZE];
        this.buffer = buffer;
        this.maxElementSize = maxElementSize;
        this.buffer = BuffersBuffer.create(memoryManager);
    }
    /**
@@ -162,7 +160,7 @@
     */
    @Override
    public void close() throws IOException {
        buffer.dispose();
        // Nothing to do
    }
    /**
@@ -388,26 +386,20 @@
            return "";
        }
        byte[] readBuffer;
        if (peekLength <= stringBuffer.length) {
            readBuffer = stringBuffer;
        } else {
            readBuffer = new byte[peekLength];
        }
        readLimiter.checkLimit(peekLength);
        buffer.get(readBuffer, 0, peekLength);
        state = ASN1.ELEMENT_READ_STATE_NEED_TYPE;
        String str;
        try {
            str = new String(readBuffer, 0, peekLength, "UTF-8");
            str = buffer.toStringContent(Charset.forName("UTF-8"), buffer.position(), buffer.position() + peekLength);
        } catch (final Exception e) {
            // TODO: I18N
            logger.warn(LocalizableMessage.raw("Unable to decode ASN.1 OCTETSTRING bytes as UTF-8 string: %s", e));
            str = new String(stringBuffer, 0, peekLength);
            str = buffer.toStringContent(Charset.defaultCharset(), buffer.position(), buffer.position() + peekLength);
        } finally {
            buffer.position(buffer.position() + peekLength);
        }
        logger.trace("READ ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)", peekType, peekLength, str);
@@ -453,12 +445,16 @@
        return this;
    }
    void appendBytesRead(final Buffer buffer) {
        this.buffer.append(buffer);
    void mark() {
        buffer.mark();
        markState = state;
        markReadLimiter = readLimiter;
    }
    void disposeBytesRead() {
        this.buffer.shrink();
    void reset() {
        buffer.reset();
        state = markState;
        readLimiter = markReadLimiter;
    }
    /**
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferWriter.java
@@ -16,10 +16,9 @@
 */
package org.forgerock.opendj.grizzly;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -30,7 +29,7 @@
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Cacheable;
import org.glassfish.grizzly.memory.ByteBufferWrapper;
import org.glassfish.grizzly.memory.MemoryManager;
import com.forgerock.opendj.util.StaticUtils;
@@ -46,7 +45,9 @@
            writeLength(parent, buffer.length());
            parent.writeByteArray(buffer.getBackingArray(), 0, buffer.length());
            buffer.clearAndTruncate(DEFAULT_MAX_INTERNAL_BUFFER_SIZE, BUFFER_INIT_SIZE);
            logger.trace("WRITE ASN.1 END SEQUENCE(length=%d)", buffer.length());
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 END SEQUENCE(length=%d)", buffer.length());
            }
            return parent;
        }
@@ -67,39 +68,13 @@
        }
        @Override
        public void writeByteArray(final byte[] bs, final int offset, final int length) throws IOException {
            buffer.appendBytes(bs, offset, length);
        }
    }
    private static final class RecyclableBuffer extends ByteBufferWrapper {
        private volatile boolean usable = true;
        private RecyclableBuffer() {
            visible = ByteBuffer.allocate(BUFFER_INIT_SIZE);
            allowBufferDispose = true;
        public void writeByteSequence(ByteSequence bs) {
            buffer.appendBytes(bs);
        }
        @Override
        public void dispose() {
            usable = true;
        }
        /**
         * Ensures that the specified number of additional bytes will fit in the
         * buffer and resizes it if necessary.
         *
         * @param size
         *            The number of additional bytes.
         */
        public void ensureAdditionalCapacity(final int size) {
            final int newCount = visible.position() + size;
            if (newCount > visible.capacity()) {
                final ByteBuffer newByteBuffer =
                        ByteBuffer.allocate(Math.max(visible.capacity() << 1, newCount));
                visible.flip();
                visible = newByteBuffer.put(visible);
            }
        public void writeByteArray(final byte[] bs, final int offset, final int length) throws IOException {
            buffer.appendBytes(bs, offset, length);
        }
    }
@@ -118,7 +93,7 @@
                child = new ChildSequenceBuffer();
                child.parent = this;
            }
            outBuffer.ensureAdditionalCapacity(1);
            ensureAdditionalCapacity(1);
            outBuffer.put(type);
            child.buffer.clear();
            return child;
@@ -126,14 +101,21 @@
        @Override
        public void writeByte(final byte b) throws IOException {
            outBuffer.ensureAdditionalCapacity(1);
            ensureAdditionalCapacity(1);
            outBuffer.put(b);
        }
        @Override
        public void writeByteSequence(ByteSequence bs) {
            ensureAdditionalCapacity(bs.length());
            bs.copyTo(outBuffer.toByteBuffer());
            outBuffer.position(outBuffer.position() + bs.length());
        }
        @Override
        public void writeByteArray(final byte[] bs, final int offset, final int length)
                throws IOException {
            outBuffer.ensureAdditionalCapacity(length);
            ensureAdditionalCapacity(length);
            outBuffer.put(bs, offset, length);
        }
    }
@@ -145,33 +127,44 @@
        void writeByte(byte b) throws IOException;
        void writeByteSequence(ByteSequence bs) throws IOException;
        void writeByteArray(byte[] bs, int offset, int length) throws IOException;
    }
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
    /** Initial size of newly created buffers. */
    private static final int BUFFER_INIT_SIZE = 1024;
    private static final int BUFFER_INIT_SIZE = 4096;
    /** Default maximum size for cached protocol/entry encoding buffers. */
    private static final int DEFAULT_MAX_INTERNAL_BUFFER_SIZE = 32 * 1024;
    /** Reset the writer. */
    void reset() {
        if (!outBuffer.usable) {
            // If the output buffer is unusable, create a new one.
            outBuffer = new RecyclableBuffer();
        if (outBuffer.capacity() > DEFAULT_MAX_INTERNAL_BUFFER_SIZE) {
            outBuffer = memoryManager.allocate(BUFFER_INIT_SIZE);
        } else {
            outBuffer.clear();
        }
        outBuffer.clear();
    }
    private final MemoryManager<Buffer> memoryManager;
    private SequenceBuffer sequenceBuffer;
    private RecyclableBuffer outBuffer;
    private Buffer outBuffer;
    private final RootSequenceBuffer rootBuffer;
    /** Creates a new ASN.1 writer that writes to a StreamWriter. */
    ASN1BufferWriter() {
    ASN1BufferWriter(MemoryManager memoryManager) {
        this.sequenceBuffer = this.rootBuffer = new RootSequenceBuffer();
        this.outBuffer = new RecyclableBuffer();
        this.memoryManager = memoryManager;
        this.outBuffer = memoryManager.allocate(BUFFER_INIT_SIZE);
    }
    void ensureAdditionalCapacity(final int size) {
        final int newCount = outBuffer.position() + size;
        if (newCount > outBuffer.capacity()) {
            outBuffer = memoryManager.reallocate(outBuffer, Math.max(outBuffer.capacity() << 1, newCount));
        }
    }
    /**
@@ -201,7 +194,7 @@
    @Override
    public void recycle() {
        sequenceBuffer = rootBuffer;
        outBuffer.clear();
        outBuffer = memoryManager.allocate(BUFFER_INIT_SIZE);
    }
    @Override
@@ -210,7 +203,9 @@
        writeLength(sequenceBuffer, 1);
        sequenceBuffer.writeByte(booleanValue ? ASN1.BOOLEAN_VALUE_TRUE : ASN1.BOOLEAN_VALUE_FALSE);
        logger.trace("WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)", type, 1, booleanValue);
        if (logger.isTraceEnabled()) {
            logger.trace("WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)", type, 1, booleanValue);
        }
        return this;
    }
@@ -238,27 +233,35 @@
                || ((intValue & 0x0000007F) == intValue)) {
            writeLength(sequenceBuffer, 1);
            sequenceBuffer.writeByte((byte) intValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 1, intValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 1, intValue);
            }
        } else if (((intValue < 0) && ((intValue & 0xFFFF8000) == 0xFFFF8000))
                || ((intValue & 0x00007FFF) == intValue)) {
            writeLength(sequenceBuffer, 2);
            sequenceBuffer.writeByte((byte) (intValue >> 8));
            sequenceBuffer.writeByte((byte) intValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 2, intValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 2, intValue);
            }
        } else if (((intValue < 0) && ((intValue & 0xFF800000) == 0xFF800000))
                || ((intValue & 0x007FFFFF) == intValue)) {
            writeLength(sequenceBuffer, 3);
            sequenceBuffer.writeByte((byte) (intValue >> 16));
            sequenceBuffer.writeByte((byte) (intValue >> 8));
            sequenceBuffer.writeByte((byte) intValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 3, intValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 3, intValue);
            }
        } else {
            writeLength(sequenceBuffer, 4);
            sequenceBuffer.writeByte((byte) (intValue >> 24));
            sequenceBuffer.writeByte((byte) (intValue >> 16));
            sequenceBuffer.writeByte((byte) (intValue >> 8));
            sequenceBuffer.writeByte((byte) intValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 4, intValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 4, intValue);
            }
        }
        return this;
    }
@@ -270,20 +273,26 @@
                || ((longValue & 0x000000000000007FL) == longValue)) {
            writeLength(sequenceBuffer, 1);
            sequenceBuffer.writeByte((byte) longValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 1, longValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 1, longValue);
            }
        } else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFF8000L) == 0xFFFFFFFFFFFF8000L))
                || ((longValue & 0x0000000000007FFFL) == longValue)) {
            writeLength(sequenceBuffer, 2);
            sequenceBuffer.writeByte((byte) (longValue >> 8));
            sequenceBuffer.writeByte((byte) longValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 2, longValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 2, longValue);
            }
        } else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFF800000L) == 0xFFFFFFFFFF800000L))
                || ((longValue & 0x00000000007FFFFFL) == longValue)) {
            writeLength(sequenceBuffer, 3);
            sequenceBuffer.writeByte((byte) (longValue >> 16));
            sequenceBuffer.writeByte((byte) (longValue >> 8));
            sequenceBuffer.writeByte((byte) longValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 3, longValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 3, longValue);
            }
        } else if (((longValue < 0) && ((longValue & 0xFFFFFFFF80000000L) == 0xFFFFFFFF80000000L))
                || ((longValue & 0x000000007FFFFFFFL) == longValue)) {
            writeLength(sequenceBuffer, 4);
@@ -291,7 +300,9 @@
            sequenceBuffer.writeByte((byte) (longValue >> 16));
            sequenceBuffer.writeByte((byte) (longValue >> 8));
            sequenceBuffer.writeByte((byte) longValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 4, longValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 4, longValue);
            }
        } else if (((longValue < 0) && ((longValue & 0xFFFFFF8000000000L) == 0xFFFFFF8000000000L))
                || ((longValue & 0x0000007FFFFFFFFFL) == longValue)) {
            writeLength(sequenceBuffer, 5);
@@ -300,7 +311,9 @@
            sequenceBuffer.writeByte((byte) (longValue >> 16));
            sequenceBuffer.writeByte((byte) (longValue >> 8));
            sequenceBuffer.writeByte((byte) longValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 5, longValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 5, longValue);
            }
        } else if (((longValue < 0) && ((longValue & 0xFFFF800000000000L) == 0xFFFF800000000000L))
                || ((longValue & 0x00007FFFFFFFFFFFL) == longValue)) {
            writeLength(sequenceBuffer, 6);
@@ -310,7 +323,9 @@
            sequenceBuffer.writeByte((byte) (longValue >> 16));
            sequenceBuffer.writeByte((byte) (longValue >> 8));
            sequenceBuffer.writeByte((byte) longValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 6, longValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 6, longValue);
            }
        } else if (((longValue < 0) && ((longValue & 0xFF80000000000000L) == 0xFF80000000000000L))
                || ((longValue & 0x007FFFFFFFFFFFFFL) == longValue)) {
            writeLength(sequenceBuffer, 7);
@@ -321,7 +336,9 @@
            sequenceBuffer.writeByte((byte) (longValue >> 16));
            sequenceBuffer.writeByte((byte) (longValue >> 8));
            sequenceBuffer.writeByte((byte) longValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 7, longValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 7, longValue);
            }
        } else {
            writeLength(sequenceBuffer, 8);
            sequenceBuffer.writeByte((byte) (longValue >> 56));
@@ -332,7 +349,9 @@
            sequenceBuffer.writeByte((byte) (longValue >> 16));
            sequenceBuffer.writeByte((byte) (longValue >> 8));
            sequenceBuffer.writeByte((byte) longValue);
            logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 8, longValue);
            if (logger.isTraceEnabled()) {
                logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 8, longValue);
            }
        }
        return this;
    }
@@ -342,7 +361,9 @@
        sequenceBuffer.writeByte(type);
        writeLength(sequenceBuffer, 0);
        logger.trace("WRITE ASN.1 NULL(type=0x%x, length=%d)", type, 0);
        if (logger.isTraceEnabled()) {
            logger.trace("WRITE ASN.1 NULL(type=0x%x, length=%d)", type, 0);
        }
        return this;
    }
@@ -353,7 +374,9 @@
        writeLength(sequenceBuffer, length);
        sequenceBuffer.writeByteArray(value, offset, length);
        logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, length);
        if (logger.isTraceEnabled()) {
            logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, length);
        }
        return this;
    }
@@ -362,12 +385,15 @@
            throws IOException {
        sequenceBuffer.writeByte(type);
        writeLength(sequenceBuffer, value.length());
        // TODO: Is there a more efficient way to do this?
        for (int i = 0; i < value.length(); i++) {
            sequenceBuffer.writeByte(value.byteAt(i));
        }
        sequenceBuffer.writeByteSequence(value);
//        // TODO: Is there a more efficient way to do this?
//        for (int i = 0; i < value.length(); i++) {
//            sequenceBuffer.writeByte(value.byteAt(i));
//        }
        logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, value.length());
        if (logger.isTraceEnabled()) {
            logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, value.length());
        }
        return this;
    }
@@ -384,7 +410,9 @@
        writeLength(sequenceBuffer, bytes.length);
        sequenceBuffer.writeByteArray(bytes, 0, bytes.length);
        logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)", type, bytes.length, value);
        if (logger.isTraceEnabled()) {
            logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)", type, bytes.length, value);
        }
        return this;
    }
@@ -393,7 +421,9 @@
        // Get a child sequence buffer
        sequenceBuffer = sequenceBuffer.startSequence(type);
        logger.trace("WRITE ASN.1 START SEQUENCE(type=0x%x)", type);
        if (logger.isTraceEnabled()) {
            logger.trace("WRITE ASN.1 START SEQUENCE(type=0x%x)", type);
        }
        return this;
    }
@@ -404,8 +434,8 @@
        return writeStartSequence(type);
    }
    Buffer getBuffer() {
        outBuffer.usable = false;
    public Buffer getBuffer() {
        outBuffer.allowBufferDispose(true);
        return outBuffer.flip();
    }
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java
@@ -12,7 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2010 Sun Microsystems, Inc.
 * Portions copyright 2011-2014 ForgeRock AS.
 * Portions copyright 2011-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
@@ -66,12 +66,7 @@
        if (useWorkerThreadsStr != null) {
            useWorkerThreadStrategy = Boolean.parseBoolean(useWorkerThreadsStr);
        } else {
            /*
             * The most best performing strategy to use is the
             * SameThreadIOStrategy, however it can only be used in cases where
             * result listeners will not block.
             */
            useWorkerThreadStrategy = true;
            useWorkerThreadStrategy = false;
        }
        if (useWorkerThreadStrategy) {
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
@@ -186,7 +186,7 @@
    }
    private LdapPromise<Void> sendAbandonRequest(final AbandonRequest request) {
        final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
        final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
        try {
            final int messageID = nextMsgID.getAndIncrement();
            writer.writeAbandonRequest(messageID, request);
@@ -212,7 +212,7 @@
                pendingRequests.put(messageID, promise);
            }
            try {
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
                try {
                    writer.writeAddRequest(messageID, request);
                    connection.write(writer.getASN1Writer().getBuffer(), null);
@@ -285,7 +285,7 @@
            }
            try {
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
                try {
                    // Use the bind client to get the initial request instead of
                    // using the bind request passed to this method.
@@ -333,7 +333,7 @@
                pendingRequests.put(messageID, promise);
            }
            try {
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
                try {
                    writer.writeCompareRequest(messageID, request);
                    connection.write(writer.getASN1Writer().getBuffer(), null);
@@ -363,7 +363,7 @@
                pendingRequests.put(messageID, promise);
            }
            try {
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
                try {
                    writer.writeDeleteRequest(messageID, request);
                    connection.write(writer.getASN1Writer().getBuffer(), null);
@@ -409,7 +409,7 @@
                pendingRequests.put(messageID, promise);
            }
            try {
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
                try {
                    writer.writeExtendedRequest(messageID, request);
                    connection.write(writer.getASN1Writer().getBuffer(), null);
@@ -454,7 +454,7 @@
                pendingRequests.put(messageID, promise);
            }
            try {
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
                try {
                    writer.writeModifyRequest(messageID, request);
                    connection.write(writer.getASN1Writer().getBuffer(), null);
@@ -484,7 +484,7 @@
                pendingRequests.put(messageID, promise);
            }
            try {
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
                try {
                    writer.writeModifyDNRequest(messageID, request);
                    connection.write(writer.getASN1Writer().getBuffer(), null);
@@ -524,7 +524,7 @@
                pendingRequests.put(messageID, promise);
            }
            try {
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
                try {
                    writer.writeSearchRequest(messageID, request);
                    connection.write(writer.getASN1Writer().getBuffer(), null);
@@ -668,7 +668,7 @@
         * connection and release resources.
         */
        if (notifyClose) {
            final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
            final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager());
            try {
                writer.writeUnbindRequest(nextMsgID.getAndIncrement(), unbindRequest);
                connection.write(writer.getASN1Writer().getBuffer(), null);
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
@@ -12,30 +12,40 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2010 Sun Microsystems, Inc.
 * Portions copyright 2011-2015 ForgeRock AS.
 * Portions copyright 2011-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.DEFAULT_TRANSPORT;
import static org.forgerock.opendj.ldap.CommonLDAPOptions.LDAP_DECODE_OPTIONS;
import static org.forgerock.opendj.ldap.LDAPListener.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.spi.LDAPListenerImpl;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage;
import org.forgerock.util.Function;
import org.forgerock.util.Options;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler;
import org.glassfish.grizzly.nio.transport.TCPNIOConnection;
import org.glassfish.grizzly.nio.transport.TCPNIOServerConnection;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import com.forgerock.opendj.util.ReferenceCountedObject;
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.Stream;
/**
 * LDAP listener implementation using Grizzly for transport.
@@ -43,80 +53,76 @@
public final class GrizzlyLDAPListener implements LDAPListenerImpl {
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
    private final ReferenceCountedObject<TCPNIOTransport>.Reference transport;
    private final ServerConnectionFactory<LDAPClientContext, Integer> connectionFactory;
    private final TCPNIOServerConnection serverConnection;
    private final Collection<TCPNIOServerConnection> serverConnections;
    private final AtomicBoolean isClosed = new AtomicBoolean();
    private final InetSocketAddress socketAddress;
    private final Set<SocketAddress> socketAddresses;
    private final Options options;
    /**
     * Creates a new LDAP listener implementation which will listen for LDAP
     * client connections using the provided address and connection options.
     * Creates a new LDAP listener implementation which will listen for LDAP client connections using the provided
     * address and connection options.
     *
     * @param address
     *            The address to listen on.
     * @param factory
     *            The server connection factory which will be used to create
     *            server connections.
     * @param addresses
     *            The addresses to listen on.
     * @param options
     *            The LDAP listener options.
     * @param handler
     *            The server connection factory which will be used to create server connections.
     * @throws IOException
     *             If an error occurred while trying to listen on the provided
     *             address.
     *             If an error occurred while trying to listen on the provided address.
     */
    public GrizzlyLDAPListener(final InetSocketAddress address,
            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
            final Options options) throws IOException {
        this(address, factory, options, null);
    public GrizzlyLDAPListener(final Set<? extends SocketAddress> addresses, final Options options,
            final Function<LDAPClientContext,
                           ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>,
                           LdapException> handler) throws IOException {
        this(addresses, handler, options, null);
    }
    /**
     * Creates a new LDAP listener implementation which will listen for LDAP
     * client connections using the provided address, connection options and
     * provided TCP transport.
     * Creates a new LDAP listener implementation which will listen for LDAP client connections using the provided
     * address, connection options and provided TCP transport.
     *
     * @param address
     *            The address to listen on.
     * @param factory
     *            The server connection factory which will be used to create
     *            server connections.
     * @param addresses
     *            The addresses to listen on.
     * @param handler
     *            The server connection factory which will be used to create server connections.
     * @param options
     *            The LDAP listener options.
     * @param transport
     *            Grizzly TCP Transport NIO implementation to use for
     *            connections. If {@code null}, default transport will be used.
     *            Grizzly TCP Transport NIO implementation to use for connections. If {@code null}, default transport
     *            will be used.
     * @throws IOException
     *             If an error occurred while trying to listen on the provided
     *             address.
     *             If an error occurred while trying to listen on the provided address.
     */
    public GrizzlyLDAPListener(final InetSocketAddress address,
            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
    public GrizzlyLDAPListener(final Set<? extends SocketAddress> addresses,
            final Function<LDAPClientContext,
                           ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>,
                           LdapException> handler,
            final Options options, TCPNIOTransport transport) throws IOException {
        this.transport = DEFAULT_TRANSPORT.acquireIfNull(transport);
        this.connectionFactory = factory;
        this.options = Options.copyOf(options);
        final LDAPServerFilter serverFilter =
                new LDAPServerFilter(this, options.get(LDAP_DECODE_OPTIONS), options.get(REQUEST_MAX_SIZE_IN_BYTES));
        final FilterChain ldapChain =
                GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), serverFilter);
        final TCPNIOBindingHandler bindingHandler =
                TCPNIOBindingHandler.builder(this.transport.get()).processor(ldapChain).build();
        this.serverConnection = bindingHandler.bind(address, options.get(CONNECT_MAX_BACKLOG));
        /*
         * Get the socket address now, ensuring that the host is the same as the
         * one provided in the constructor. The port will have changed if 0 was
         * passed in.
         */
        final int port = ((InetSocketAddress) serverConnection.getLocalAddress()).getPort();
        socketAddress = new InetSocketAddress(Connections.getHostString(address), port);
        final LDAPServerFilter serverFilter = new LDAPServerFilter(handler, options,
                options.get(LDAP_DECODE_OPTIONS), options.get(MAX_CONCURRENT_REQUESTS));
        final FilterChain ldapChain = GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(),
                new LdapCodec(options.get(REQUEST_MAX_SIZE_IN_BYTES), options.get(LDAP_DECODE_OPTIONS)), serverFilter);
        final TCPNIOBindingHandler bindingHandler = TCPNIOBindingHandler.builder(this.transport.get())
                .processor(ldapChain).build();
        this.serverConnections = new ArrayList<>(addresses.size());
        this.socketAddresses = new HashSet<>(addresses.size());
        for (final SocketAddress address : addresses) {
            final TCPNIOServerConnection bound = bindingHandler.bind(address, options.get(CONNECT_MAX_BACKLOG));
            serverConnections.add(bound);
            socketAddresses.add(bound.getLocalAddress());
        }
    }
    @Override
    public void close() {
        if (isClosed.compareAndSet(false, true)) {
            try {
                serverConnection.close().get();
                for (TCPNIOConnection serverConnection : serverConnections) {
                    serverConnection.close().get();
                }
            } catch (final InterruptedException e) {
                // Cannot handle here.
                Thread.currentThread().interrupt();
@@ -129,23 +135,19 @@
    }
    @Override
    public InetSocketAddress getSocketAddress() {
        return socketAddress;
    public Set<? extends SocketAddress> getSocketAddresses() {
        return socketAddresses;
    }
    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("LDAPListener(");
        builder.append(getSocketAddress());
        builder.append(socketAddresses);
        builder.append(')');
        return builder.toString();
    }
    ServerConnectionFactory<LDAPClientContext, Integer> getConnectionFactory() {
        return connectionFactory;
    }
    Options getLDAPListenerOptions() {
        return options;
    }
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java
@@ -11,7 +11,7 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2015 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
@@ -31,7 +31,9 @@
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.FilterChainEnabledTransport;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.memory.BuffersBuffer;
import org.glassfish.grizzly.memory.MemoryManager;
import org.glassfish.grizzly.nio.transport.TCPNIOConnection;
import org.glassfish.grizzly.ssl.SSLFilter;
@@ -65,11 +67,14 @@
     *         is a {@code FilterChain}, and having the provided filter as the
     *         last filter
     */
    static FilterChain buildFilterChain(Processor<?> processor, Filter filter) {
    static FilterChain buildFilterChain(Processor<?> processor, Filter... filters) {
        if (processor instanceof FilterChain) {
            return FilterChainBuilder.stateless().addAll((FilterChain) processor).add(filter).build();
            return FilterChainBuilder.stateless().addAll((FilterChain) processor).addAll(filters).build();
        } else if (processor instanceof FilterChainEnabledTransport) {
            return FilterChainBuilder.stateless().add(((FilterChainEnabledTransport) processor).getTransportFilter())
                                                 .addAll(filters).build();
        } else {
            return FilterChainBuilder.stateless().add(new TransportFilter()).add(filter).build();
            return FilterChainBuilder.stateless().add(new TransportFilter()).addAll(filters).build();
        }
    }
@@ -113,17 +118,14 @@
     */
    static FilterChain addFilterToChain(final Filter filter, final FilterChain chain) {
        // By default, before LDAP filter which is the last one
        int indexToAddFilter = chain.size() - 1;
        if (filter instanceof SSLFilter) {
            // Before any ConnectionSecurityLayerFilters if present
            for (int i = chain.size() - 2; i >= 0; i--) {
                if (!(chain.get(i) instanceof ConnectionSecurityLayerFilter)) {
                    indexToAddFilter = i + 1;
                    break;
                }
            }
            return FilterChainBuilder.stateless().addAll(chain).add(1, filter).build();
        }
        return FilterChainBuilder.stateless().addAll(chain).add(indexToAddFilter, filter).build();
        if (filter instanceof SaslFilter) {
            final int pos = chain.get(1) instanceof SSLFilter ? 2 : 1;
            return FilterChainBuilder.stateless().addAll(chain).add(pos, filter).build();
        }
        return FilterChainBuilder.stateless().addAll(chain).add(chain.size() - 1, filter).build();
    }
    /**
@@ -141,7 +143,7 @@
     */
    static LDAPReader<ASN1BufferReader> createReader(DecodeOptions decodeOptions,
            int maxASN1ElementSize, MemoryManager<?> memoryManager) {
        ASN1BufferReader asn1Reader = new ASN1BufferReader(maxASN1ElementSize, memoryManager);
        ASN1BufferReader asn1Reader = new ASN1BufferReader(maxASN1ElementSize, BuffersBuffer.create(memoryManager));
        return LDAP.getReader(asn1Reader, decodeOptions);
    }
@@ -155,10 +157,10 @@
     * @return a LDAP writer
     */
    @SuppressWarnings("unchecked")
    static LDAPWriter<ASN1BufferWriter> getWriter() {
    static LDAPWriter<ASN1BufferWriter> getWriter(final MemoryManager memoryManager) {
        LDAPWriter<ASN1BufferWriter> writer = ThreadCache.takeFromCache(WRITER_INDEX);
        if (writer == null) {
            writer = LDAP.getWriter(new ASN1BufferWriter());
            writer = LDAP.getWriter(new ASN1BufferWriter(memoryManager));
        }
        writer.getASN1Writer().reset();
        return writer;
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPBaseFilter.java
@@ -11,25 +11,15 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
import java.io.IOException;
import org.forgerock.opendj.io.LDAPMessageHandler;
import org.forgerock.opendj.io.LDAPReader;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
/**
 * Base class for LDAP-enabled filter.
 * <p>
 * Provides a common {@code handleRead()} method for both client and server
 * filters.
 */
abstract class LDAPBaseFilter extends BaseFilter {
@@ -58,64 +48,4 @@
        this.decodeOptions = options;
        this.maxASN1ElementSize = maxASN1ElementSize;
    }
    @Override
    public final NextAction handleRead(final FilterChainContext ctx) throws IOException {
        final LDAPBaseHandler handler = getLDAPHandler(ctx);
        final LDAPReader<ASN1BufferReader> reader = handler.getReader();
        final ASN1BufferReader asn1Reader = reader.getASN1Reader();
        final Buffer buffer = (Buffer) ctx.getMessage();
        asn1Reader.appendBytesRead(buffer);
        try {
            while (reader.hasMessageAvailable()) {
                reader.readMessage(handler);
            }
        } catch (IOException e) {
            handleReadException(ctx, e);
            throw e;
        } finally {
            asn1Reader.disposeBytesRead();
        }
        return ctx.getStopAction();
    }
    /**
     * Handle an exception occuring during a read within the
     * {@code handleRead()} method.
     *
     * @param ctx
     *            context when reading
     * @param e
     *            exception occuring while reading
     */
    abstract void handleReadException(FilterChainContext ctx, IOException e);
    /**
     * Interface for the {@code LDAPMessageHandler} used in the filter, that
     * must be able to retrieve a Grizzly reader.
     */
    interface LDAPBaseHandler extends LDAPMessageHandler {
        /**
         * Returns the LDAP reader for this handler.
         * @return the reader
         */
        LDAPReader<ASN1BufferReader> getReader();
    }
    /**
     * Returns the LDAP message handler associated to the underlying connection
     * of the provided context.
     * <p>
     * If no handler exists yet for the underlying connection, a new one is
     * created and recorded for the connection.
     *
     * @param ctx
     *            current filter chain context
     * @return the response handler associated to the connection, which can be a
     *         new one if no handler have been created yet
     */
    abstract LDAPBaseHandler getLDAPHandler(final FilterChainContext ctx);
}
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
@@ -17,13 +17,19 @@
package org.forgerock.opendj.grizzly;
import static org.forgerock.opendj.ldap.CommonLDAPOptions.LDAP_DECODE_OPTIONS;
import static org.forgerock.opendj.ldap.ResultCode.CLIENT_SIDE_LOCAL_ERROR;
import static org.forgerock.opendj.ldap.responses.Responses.newResult;
import java.io.EOFException;
import java.io.IOException;
import javax.net.ssl.SSLEngine;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.AbstractLDAPMessageHandler;
import org.forgerock.opendj.io.LDAP;
import org.forgerock.opendj.io.LDAPMessageHandler;
import org.forgerock.opendj.io.LDAPReader;
import org.forgerock.opendj.io.LDAPWriter;
import org.forgerock.opendj.ldap.ConnectionSecurityLayer;
@@ -53,6 +59,7 @@
import org.forgerock.opendj.ldap.spi.ExtendedResultLdapPromiseImpl;
import org.forgerock.opendj.ldap.spi.ResultLdapPromiseImpl;
import org.forgerock.opendj.ldap.spi.SearchResultLdapPromiseImpl;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.Grizzly;
@@ -60,10 +67,6 @@
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.opendj.ldap.ResultCode.*;
import static org.forgerock.opendj.ldap.responses.Responses.*;
/**
 * Grizzly filter implementation for decoding LDAP responses and handling client
 * side logic for SSL and SASL operations over LDAP.
@@ -75,36 +78,14 @@
    private static final Attribute<ClientResponseHandler> RESPONSE_HANDLER_ATTR =
            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ClientResponseHandler");
    static final class ClientResponseHandler extends AbstractLDAPMessageHandler implements
            LDAPBaseHandler {
    static final class ClientResponseHandler extends AbstractLDAPMessageHandler {
        private final LDAPReader<ASN1BufferReader> reader;
        private FilterChainContext context;
        /**
         * Creates a handler with the provided reader.
         *
         * @param reader
         *            LDAP reader to use for reading incoming messages
         */
        ClientResponseHandler(LDAPReader<ASN1BufferReader> reader) {
            this.reader = reader;
        }
        void setFilterChainContext(FilterChainContext context) {
            this.context = context;
        }
        /**
         * Returns the LDAP reader.
         *
         * @return the reader to read incoming LDAP messages
         */
        @Override
        public LDAPReader<ASN1BufferReader> getReader() {
            return this.reader;
        }
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override
        public void addResult(final int messageID, final Result result) throws DecodeException,
@@ -139,7 +120,8 @@
                                // bind response.
                                final int msgID = ldapConnection.continuePendingBindRequest(promise);
                                LDAPWriter<ASN1BufferWriter> ldapWriter = GrizzlyUtils.getWriter();
                                LDAPWriter<ASN1BufferWriter> ldapWriter =
                                        GrizzlyUtils.getWriter(context.getMemoryManager());
                                try {
                                    final GenericBindRequest nextRequest =
                                            bindClient.nextBindRequest();
@@ -435,6 +417,32 @@
    }
    @Override
    public final NextAction handleRead(final FilterChainContext ctx) throws IOException {
        final LDAPMessageHandler handler = getLDAPHandler(ctx);
        final Buffer buffer = (Buffer) ctx.getMessage();
        try (final ASN1BufferReader reader = new ASN1BufferReader(maxASN1ElementSize, buffer)) {
            final LDAPReader<? extends ASN1Reader> ldapReader = LDAP.getReader(reader, decodeOptions);
            while (ldapReader.hasMessageAvailable()) {
                ldapReader.readMessage(handler);
            }
            buffer.shrink();
        } catch (IOException e) {
            handleReadException(ctx, e);
            throw e;
        }
        return ctx.getStopAction(buffer.hasRemaining() ? buffer : null);
    }
    private final void handleReadException(FilterChainContext ctx, IOException e) {
        final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(ctx.getConnection());
        final Result errorResult =
                Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(e)
                        .setDiagnosticMessage(e.getMessage());
        ldapConnection.close(null, false, errorResult);
    }
    @Override
    public NextAction handleClose(final FilterChainContext ctx) throws IOException {
        final Connection<?> connection = ctx.getConnection();
        final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.remove(connection);
@@ -445,15 +453,6 @@
        return ctx.getInvokeAction();
    }
    @Override
    final void handleReadException(FilterChainContext ctx, IOException e) {
        final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(ctx.getConnection());
        final Result errorResult =
                Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(e)
                        .setDiagnosticMessage(e.getMessage());
        ldapConnection.close(null, false, errorResult);
    }
    /**
     * Returns the response handler associated to the provided connection and
     * context.
@@ -466,15 +465,11 @@
     * @return the response handler associated to the context, which can be a
     *         new one if no handler have been created yet
     */
    @Override
    final LDAPBaseHandler getLDAPHandler(final FilterChainContext ctx) {
    final LDAPMessageHandler getLDAPHandler(final FilterChainContext ctx) {
        Connection<?> connection = ctx.getConnection();
        ClientResponseHandler handler = RESPONSE_HANDLER_ATTR.get(connection);
        if (handler == null) {
            LDAPReader<ASN1BufferReader> reader =
                    GrizzlyUtils.createReader(decodeOptions, maxASN1ElementSize, connection
                            .getTransport().getMemoryManager());
            handler = new ClientResponseHandler(reader);
            handler = new ClientResponseHandler();
            RESPONSE_HANDLER_ATTR.set(connection, handler);
        }
        handler.setFilterChainContext(ctx);
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
@@ -16,584 +16,80 @@
 */
package org.forgerock.opendj.grizzly;
import static com.forgerock.reactive.RxJavaStreams.*;
import static org.forgerock.opendj.grizzly.GrizzlyUtils.configureConnection;
import static org.forgerock.opendj.io.LDAP.*;
import static org.forgerock.opendj.ldap.spi.LdapMessages.newResponseMessage;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.GeneralSecurityException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslServer;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.io.AbstractLDAPMessageHandler;
import org.forgerock.opendj.io.LDAP;
import org.forgerock.opendj.io.LDAPReader;
import org.forgerock.opendj.io.LDAPWriter;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConnectionSecurityLayer;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.ServerConnection;
import org.forgerock.opendj.ldap.TrustManagers;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.CompareRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
import org.forgerock.opendj.ldap.requests.GenericBindRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.requests.UnbindRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.GenericExtendedResult;
import org.forgerock.opendj.ldap.responses.IntermediateResponse;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
import org.forgerock.opendj.ldap.spi.LdapMessages;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapResponseMessage;
import org.forgerock.util.Function;
import org.forgerock.util.Options;
import org.forgerock.util.Reject;
import org.glassfish.grizzly.CloseReason;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.attributes.Attribute;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.grizzly.ssl.SSLFilter;
import org.glassfish.grizzly.ssl.SSLUtils;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import static org.forgerock.opendj.grizzly.GrizzlyUtils.*;
import com.forgerock.reactive.Completable;
import com.forgerock.reactive.Completable.Emitter;
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.Stream;
import io.reactivex.internal.util.BackpressureHelper;
/**
 * Grizzly filter implementation for decoding LDAP requests and handling server
 * side logic for SSL and SASL operations over LDAP.
 * Grizzly filter implementation for decoding LDAP requests and handling server side logic for SSL and SASL operations
 * over LDAP.
 */
final class LDAPServerFilter extends LDAPBaseFilter {
final class LDAPServerFilter extends BaseFilter {
    /** Provides an arbitrary write operation on a LDAP writer. */
    private interface LDAPWrite<T> {
        void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, T message)
                throws IOException;
    }
    private static final Attribute<ClientConnectionImpl> LDAP_CONNECTION_ATTR = Grizzly.DEFAULT_ATTRIBUTE_BUILDER
            .createAttribute("LDAPServerConnection");
    /** Write operation for intermediate responses. */
    private static final LDAPWrite<IntermediateResponse> INTERMEDIATE =
            new LDAPWrite<IntermediateResponse>() {
                @Override
                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID,
                        IntermediateResponse resp) throws IOException {
                    writer.writeIntermediateResponse(messageID, resp);
                }
            };
    private static abstract class AbstractHandler<R extends Result> implements
            IntermediateResponseHandler, LdapResultHandler<R> {
        protected final ClientContextImpl context;
        protected final int messageID;
        protected AbstractHandler(final ClientContextImpl context, final int messageID) {
            this.messageID = messageID;
            this.context = context;
        }
        @Override
        public void handleResult(final R result) {
            defaultHandleResult(result);
        }
        @Override
        public final boolean handleIntermediateResponse(final IntermediateResponse response) {
            writeMessage(INTERMEDIATE, response);
            return true;
        }
        /**
         * Default implementation of result handling, that delegate the actual
         * write operation to {@code writeResult} method.
         */
        private void defaultHandleResult(final R result) {
            writeMessage(new LDAPWrite<R>() {
                @Override
                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, R res)
                        throws IOException {
                    writeResult(writer, res);
                }
            }, result);
        }
        /**
         * Write a result to provided LDAP writer.
         *
         * @param ldapWriter
         *            provided writer
         * @param result
         *            to write
         * @throws IOException
         *             if an error occurs during writing
         */
        protected abstract void writeResult(final LDAPWriter<ASN1BufferWriter> ldapWriter,
                final R result) throws IOException;
        /**
         * Write a message on LDAP writer.
         *
         * @param <T>
         *            type of message to write
         * @param ldapWrite
         *            the specific write operation
         * @param message
         *            the message to write
         */
        protected final <T> void writeMessage(final LDAPWrite<T> ldapWrite, final T message) {
            final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
            try {
                ldapWrite.perform(writer, messageID, message);
                context.write(writer);
            } catch (final IOException ioe) {
                context.handleException(ioe);
            } finally {
                GrizzlyUtils.recycleWriter(writer);
            }
        }
        /**
         * Copy diagnostic message, matched DN and cause to new result from the
         * given result.
         *
         * @param newResult
         *            to update
         * @param result
         *            contains parameters to copy
         */
        protected final void populateNewResultFromResult(final R newResult, final Result result) {
            newResult.setDiagnosticMessage(result.getDiagnosticMessage());
            newResult.setMatchedDN(result.getMatchedDN());
            newResult.setCause(result.getCause());
            for (final Control control : result.getControls()) {
                newResult.addControl(control);
            }
        }
    }
    private static final class AddHandler extends AbstractHandler<Result> {
        private AddHandler(final ClientContextImpl context, final int messageID) {
            super(context, messageID);
        }
        @Override
        public void handleException(final LdapException error) {
            handleResult(error.getResult());
        }
        @Override
        public void writeResult(LDAPWriter<ASN1BufferWriter> writer, final Result result)
                throws IOException {
            writer.writeAddResult(messageID, result);
        }
    }
    private static final class BindHandler extends AbstractHandler<BindResult> {
        private BindHandler(final ClientContextImpl context, final int messageID) {
            super(context, messageID);
        }
        @Override
        public void handleException(final LdapException error) {
            final Result result = error.getResult();
            if (result instanceof BindResult) {
                handleResult((BindResult) result);
            } else {
                final BindResult newResult = Responses.newBindResult(result.getResultCode());
                populateNewResultFromResult(newResult, result);
                handleResult(newResult);
            }
        }
        @Override
        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, BindResult result)
                throws IOException {
            writer.writeBindResult(messageID, result);
        }
    }
    private static final class ClientContextImpl implements LDAPClientContext {
        private final Connection<?> connection;
        private final AtomicBoolean isClosed = new AtomicBoolean();
        private ServerConnection<Integer> serverConnection;
        private ClientContextImpl(final Connection<?> connection) {
            this.connection = connection;
        }
        @Override
        public void disconnect() {
            disconnect0(null, null);
        }
        @Override
        public void disconnect(final ResultCode resultCode, final String message) {
            Reject.ifNull(resultCode);
            final GenericExtendedResult notification =
                    Responses.newGenericExtendedResult(resultCode).setOID(
                            LDAP.OID_NOTICE_OF_DISCONNECTION).setDiagnosticMessage(message);
            sendUnsolicitedNotification(notification);
            disconnect0(resultCode, message);
        }
        @Override
        public void enableConnectionSecurityLayer(final ConnectionSecurityLayer layer) {
            synchronized (this) {
                installFilter(new ConnectionSecurityLayerFilter(layer, connection.getTransport()
                        .getMemoryManager()));
            }
        }
        @Override
        public void enableTLS(final SSLContext sslContext, final String[] protocols,
                final String[] suites, final boolean wantClientAuth, final boolean needClientAuth) {
            Reject.ifNull(sslContext);
            synchronized (this) {
                if (isTLSEnabled()) {
                    throw new IllegalStateException("TLS already enabled");
                }
                final SSLEngineConfigurator sslEngineConfigurator =
                        new SSLEngineConfigurator(sslContext, false, false, false);
                sslEngineConfigurator.setEnabledCipherSuites(suites);
                sslEngineConfigurator.setEnabledProtocols(protocols);
                sslEngineConfigurator.setWantClientAuth(wantClientAuth);
                sslEngineConfigurator.setNeedClientAuth(needClientAuth);
                installFilter(new SSLFilter(sslEngineConfigurator, DUMMY_SSL_ENGINE_CONFIGURATOR));
            }
        }
        @Override
        public InetSocketAddress getLocalAddress() {
            return (InetSocketAddress) connection.getLocalAddress();
        }
        @Override
        public InetSocketAddress getPeerAddress() {
            return (InetSocketAddress) connection.getPeerAddress();
        }
        @Override
        public int getSecurityStrengthFactor() {
            final SSLSession sslSession = getSSLSession();
            if (sslSession != null) {
                final String cipherString = sslSession.getCipherSuite();
                for (final Object[] cipher : CIPHER_KEY_SIZES) {
                    if (cipherString.contains((String) cipher[0])) {
                        return (Integer) cipher[1];
                    }
                }
            }
            return 0;
        }
        @Override
        public SSLSession getSSLSession() {
            final SSLEngine sslEngine = SSLUtils.getSSLEngine(connection);
            return sslEngine != null ? sslEngine.getSession() : null;
        }
        @Override
        public boolean isClosed() {
            return isClosed.get();
        }
        @Override
        public void sendUnsolicitedNotification(final ExtendedResult notification) {
            LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
            try {
                writer.writeExtendedResult(0, notification);
                connection.write(writer.getASN1Writer().getBuffer(), null);
            } catch (final IOException ioe) {
                handleException(ioe);
            } finally {
                GrizzlyUtils.recycleWriter(writer);
            }
        }
        @Override
        public String toString() {
            final StringBuilder builder = new StringBuilder();
            builder.append("LDAPClientContext(");
            builder.append(getLocalAddress());
            builder.append(',');
            builder.append(getPeerAddress());
            builder.append(')');
            return builder.toString();
        }
        public void write(final LDAPWriter<ASN1BufferWriter> writer) {
            connection.write(writer.getASN1Writer().getBuffer(), null);
        }
        private void disconnect0(final ResultCode resultCode, final String message) {
            // Close this connection context.
            if (isClosed.compareAndSet(false, true)) {
                try {
                    // Notify the server connection: it may be null if disconnect is
                    // invoked during accept.
                    if (serverConnection != null) {
                        serverConnection.handleConnectionDisconnected(resultCode, message);
                    }
                } finally {
                    // Close the connection.
                    connection.closeSilently();
                }
            }
        }
        private ServerConnection<Integer> getServerConnection() {
            return serverConnection;
        }
        private void handleClose(final int messageID, final UnbindRequest unbindRequest) {
            // Close this connection context.
            if (isClosed.compareAndSet(false, true)) {
                try {
                    // Notify the server connection: it may be null if disconnect is
                    // invoked during accept.
                    if (serverConnection != null) {
                        serverConnection.handleConnectionClosed(messageID, unbindRequest);
                    }
                } finally {
                    // If this close was a result of an unbind request then the
                    // connection won't actually be closed yet. To avoid TIME_WAIT TCP
                    // state, let the client disconnect.
                    if (unbindRequest != null) {
                        return;
                    }
                    // Close the connection.
                    connection.closeSilently();
                }
            }
        }
        private void handleException(final Throwable error) {
            // Close this connection context.
            if (isClosed.compareAndSet(false, true)) {
                try {
                    // Notify the server connection: it may be null if disconnect is
                    // invoked during accept.
                    if (serverConnection != null) {
                        serverConnection.handleConnectionError(error);
                    }
                } finally {
                    // Close the connection.
                    connection.closeSilently();
                }
            }
        }
        /**
         * Installs a new Grizzly filter (e.g. SSL/SASL) beneath the top-level
         * LDAP filter.
         *
         * @param filter
         *            The filter to be installed.
         */
        private void installFilter(final Filter filter) {
            GrizzlyUtils.addFilterToConnection(filter, connection);
        }
        /**
         * Indicates whether TLS is enabled this provided connection.
         *
         * @return {@code true} if TLS is enabled on this connection, otherwise
         *         {@code false}.
         */
        private boolean isTLSEnabled() {
            synchronized (this) {
                final FilterChain currentFilterChain = (FilterChain) connection.getProcessor();
                for (final Filter filter : currentFilterChain) {
                    if (filter instanceof SSLFilter) {
                        return true;
                    }
                }
                return false;
            }
        }
        private void setServerConnection(final ServerConnection<Integer> serverConnection) {
            this.serverConnection = serverConnection;
        }
    }
    private static final class CompareHandler extends AbstractHandler<CompareResult> {
        private CompareHandler(final ClientContextImpl context, final int messageID) {
            super(context, messageID);
        }
        @Override
        public void handleException(final LdapException error) {
            final Result result = error.getResult();
            if (result instanceof CompareResult) {
                handleResult((CompareResult) result);
            } else {
                final CompareResult newResult = Responses.newCompareResult(result.getResultCode());
                populateNewResultFromResult(newResult, result);
                handleResult(newResult);
            }
        }
        @Override
        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, CompareResult result)
                throws IOException {
            writer.writeCompareResult(messageID, result);
        }
    }
    private static final class DeleteHandler extends AbstractHandler<Result> {
        private DeleteHandler(final ClientContextImpl context, final int messageID) {
            super(context, messageID);
        }
        @Override
        public void handleException(final LdapException error) {
            handleResult(error.getResult());
        }
        @Override
        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result)
                throws IOException {
            writer.writeDeleteResult(messageID, result);
        }
    }
    private static final class ExtendedHandler<R extends ExtendedResult> extends AbstractHandler<R> {
        private ExtendedHandler(final ClientContextImpl context, final int messageID) {
            super(context, messageID);
        }
        @Override
        public void handleException(final LdapException error) {
            final Result result = error.getResult();
            if (result instanceof ExtendedResult) {
                handleResult((ExtendedResult) result);
            } else {
                final ExtendedResult newResult =
                        Responses.newGenericExtendedResult(result.getResultCode());
                newResult.setDiagnosticMessage(result.getDiagnosticMessage());
                newResult.setMatchedDN(result.getMatchedDN());
                newResult.setCause(result.getCause());
                for (final Control control : result.getControls()) {
                    newResult.addControl(control);
                }
                handleResult(newResult);
            }
        }
        @Override
        public void handleResult(final ExtendedResult result) {
            writeMessage(new LDAPWrite<ExtendedResult>() {
                @Override
                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID,
                        ExtendedResult message) throws IOException {
                    writer.writeExtendedResult(messageID, message);
                }
            }, result);
        }
        @Override
        protected void writeResult(LDAPWriter<ASN1BufferWriter> ldapWriter, R result)
                throws IOException {
            // never called because handleResult(result) method is overriden in this class
        }
    }
    private static final class ModifyDNHandler extends AbstractHandler<Result> {
        private ModifyDNHandler(final ClientContextImpl context, final int messageID) {
            super(context, messageID);
        }
        @Override
        public void handleException(final LdapException error) {
            handleResult(error.getResult());
        }
        @Override
        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result)
                throws IOException {
            writer.writeModifyDNResult(messageID, result);
        }
    }
    private static final class ModifyHandler extends AbstractHandler<Result> {
        private ModifyHandler(final ClientContextImpl context, final int messageID) {
            super(context, messageID);
        }
        @Override
        public void handleException(final LdapException error) {
            handleResult(error.getResult());
        }
        @Override
        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result)
                throws IOException {
            writer.writeModifyResult(messageID, result);
        }
    }
    private static final class SearchHandler extends AbstractHandler<Result> implements
            SearchResultHandler {
        private SearchHandler(final ClientContextImpl context, final int messageID) {
            super(context, messageID);
        }
        @Override
        public boolean handleEntry(final SearchResultEntry entry) {
            writeMessage(new LDAPWrite<SearchResultEntry>() {
                @Override
                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID,
                        SearchResultEntry sre) throws IOException {
                    writer.writeSearchResultEntry(messageID, sre);
                }
            }, entry);
            return true;
        }
        @Override
        public void handleException(final LdapException error) {
            handleResult(error.getResult());
        }
        @Override
        public boolean handleReference(final SearchResultReference reference) {
            writeMessage(new LDAPWrite<SearchResultReference>() {
                @Override
                public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID,
                        SearchResultReference ref) throws IOException {
                    writer.writeSearchResultReference(messageID, ref);
                }
            }, reference);
            return true;
        }
        @Override
        protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result)
                throws IOException {
            writer.writeSearchResult(messageID, result);
        }
    }
    // @formatter:off
    private final Function<LDAPClientContext,
                           ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>,
                           LdapException> connectionHandler;
    /**
     * Map of cipher phrases to effective key size (bits). Taken from the
@@ -620,259 +116,424 @@
        { "_WITH_NULL_",             0 },
    };
    // @formatter:on
    /** Default maximum request size for incoming requests. */
    private static final int DEFAULT_MAX_REQUEST_SIZE = 5 * 1024 * 1024;
    private static final Attribute<ClientContextImpl> LDAP_CONNECTION_ATTR =
            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPServerConnection");
    private static final Attribute<ServerRequestHandler> REQUEST_HANDLER_ATTR =
            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ServerRequestHandler");
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
    /**
     * A dummy SSL client engine configurator as SSLFilter only needs server
     * config. This prevents Grizzly from needlessly using JVM defaults which
     * may be incorrectly configured.
     */
    private static final SSLEngineConfigurator DUMMY_SSL_ENGINE_CONFIGURATOR;
    static {
        try {
            DUMMY_SSL_ENGINE_CONFIGURATOR =
                    new SSLEngineConfigurator(new SSLContextBuilder().setTrustManager(
                            TrustManagers.distrustAll()).getSSLContext());
        } catch (GeneralSecurityException e) {
            // This should never happen.
            throw new IllegalStateException("Unable to create Dummy SSL Engine Configurator", e);
        }
    }
    private final GrizzlyLDAPListener listener;
    private static final class ServerRequestHandler extends AbstractLDAPMessageHandler implements
            LDAPBaseHandler {
        private final Connection<?> connection;
        private final LDAPReader<ASN1BufferReader> reader;
        /**
         * Creates the handler with a connection.
         *
         * @param connection
         *            connection this handler is associated with
         * @param reader
         *            LDAP reader to use for reading incoming messages
         */
        ServerRequestHandler(Connection<?> connection, LDAPReader<ASN1BufferReader> reader) {
            this.connection = connection;
            this.reader = reader;
        }
        /**
         * Returns the LDAP reader.
         *
         * @return the reader to read incoming LDAP messages
         */
        @Override
        public LDAPReader<ASN1BufferReader> getReader() {
            return reader;
        }
        @Override
        public void abandonRequest(final int messageID, final AbandonRequest request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                conn.handleAbandon(messageID, request);
            }
        }
        @Override
        public void addRequest(final int messageID, final AddRequest request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final AddHandler handler = new AddHandler(clientContext, messageID);
                conn.handleAdd(messageID, request, handler, handler);
            }
        }
        @Override
        public void bindRequest(final int messageID, final int version,
                final GenericBindRequest request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final AbstractHandler<BindResult> handler =
                        new BindHandler(clientContext, messageID);
                conn.handleBind(messageID, version, request, handler, handler);
            }
        }
        @Override
        public void compareRequest(final int messageID, final CompareRequest request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final CompareHandler handler = new CompareHandler(clientContext, messageID);
                conn.handleCompare(messageID, request, handler, handler);
            }
        }
        @Override
        public void deleteRequest(final int messageID, final DeleteRequest request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final DeleteHandler handler = new DeleteHandler(clientContext, messageID);
                conn.handleDelete(messageID, request, handler, handler);
            }
        }
        @Override
        public <R extends ExtendedResult> void extendedRequest(final int messageID,
                final ExtendedRequest<R> request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final ExtendedHandler<R> handler = new ExtendedHandler<>(clientContext, messageID);
                conn.handleExtendedRequest(messageID, request, handler, handler);
            }
        }
        @Override
        public void modifyDNRequest(final int messageID, final ModifyDNRequest request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final ModifyDNHandler handler = new ModifyDNHandler(clientContext, messageID);
                conn.handleModifyDN(messageID, request, handler, handler);
            }
        }
        @Override
        public void modifyRequest(final int messageID, final ModifyRequest request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final ModifyHandler handler = new ModifyHandler(clientContext, messageID);
                conn.handleModify(messageID, request, handler, handler);
            }
        }
        @Override
        public void searchRequest(final int messageID, final SearchRequest request) {
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection);
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final SearchHandler handler = new SearchHandler(clientContext, messageID);
                conn.handleSearch(messageID, request, handler, handler, handler);
            }
        }
        @Override
        public void unbindRequest(final int messageID, final UnbindRequest request) {
            // Remove the client context causing any subsequent LDAP
            // traffic to be ignored.
            final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.remove(connection);
            if (clientContext != null) {
                clientContext.handleClose(messageID, request);
            }
        }
        @Override
        public void unrecognizedMessage(final int messageID, final byte messageTag,
                final ByteString messageBytes) {
            exceptionOccurred(connection, newUnsupportedMessageException(messageID, messageTag,
                    messageBytes));
        }
    }
    private final int maxConcurrentRequests;
    private final Options connectionOptions;
    /**
     * Creates a server filter with provided listener, options and max size of
     * ASN1 element.
     * Creates a server filter with provided listener, options and max size of ASN1 element.
     *
     * @param listener
     *            listen for incoming connections
     * @param options
     *            control how to decode requests and responses
     * @param maxASN1ElementSize
     *            The maximum BER element size, or <code>0</code> to indicate
     *            that there is no limit.
     *            The maximum BER element size, or <code>0</code> to indicate that there is no limit.
     */
    LDAPServerFilter(final GrizzlyLDAPListener listener, final DecodeOptions options,
            final int maxASN1ElementSize) {
        super(options, maxASN1ElementSize <= 0 ? DEFAULT_MAX_REQUEST_SIZE : maxASN1ElementSize);
        this.listener = listener;
    LDAPServerFilter(
            Function<LDAPClientContext,
                     ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>,
                     LdapException> connectionHandler,
            final Options connectionOptions, final DecodeOptions options, final int maxPendingRequests) {
        this.connectionHandler = connectionHandler;
        this.connectionOptions = connectionOptions;
        this.maxConcurrentRequests = maxPendingRequests;
    }
    @Override
    public void exceptionOccurred(final FilterChainContext ctx, final Throwable error) {
        exceptionOccurred(ctx.getConnection(), error);
    }
    private static void exceptionOccurred(final Connection<?> connection, final Throwable error) {
        final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.remove(connection);
        if (clientContext != null) {
            clientContext.handleException(error);
        }
        LDAP_CONNECTION_ATTR.remove(ctx.getConnection()).upstream.onError(error);
    }
    @Override
    public NextAction handleAccept(final FilterChainContext ctx) throws IOException {
        final Connection<?> connection = ctx.getConnection();
        Options options = listener.getLDAPListenerOptions();
        configureConnection(connection, logger, options);
        try {
            final ClientContextImpl clientContext = new ClientContextImpl(connection);
            final ServerConnection<Integer> serverConn =
                    listener.getConnectionFactory().handleAccept(clientContext);
            clientContext.setServerConnection(serverConn);
            LDAP_CONNECTION_ATTR.set(connection, clientContext);
        } catch (final LdapException e) {
            connection.close();
        }
        configureConnection(connection, logger, connectionOptions);
        final ClientConnectionImpl clientContext = new ClientConnectionImpl(connection);
        LDAP_CONNECTION_ATTR.set(connection, clientContext);
        final ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>> handler = connectionHandler
                .apply(clientContext);
        streamFromPublisher(clientContext).flatMap(new Function<LdapRawMessage, Publisher<Integer>, Exception>() {
            @Override
            public Publisher<Integer> apply(final LdapRawMessage rawRequest) throws Exception {
                return handler
                        .handle(clientContext, rawRequest)
                        .flatMap(new Function<Stream<Response>, Publisher<Integer>, Exception>() {
                            @Override
                            public Publisher<Integer> apply(final Stream<Response> response) {
                                return clientContext.write(response.onErrorResumeWith(toErrorMessage(rawRequest))
                                                                   .map(toResponseMessage(rawRequest))).toSingle(1);
                            }
                        });
            }
        }, maxConcurrentRequests).subscribe();
        return ctx.getStopAction();
    }
    private Function<Throwable, Publisher<Response>, Exception> toErrorMessage(final LdapRawMessage rawRequest) {
        return new Function<Throwable, Publisher<Response>, Exception>() {
            @Override
            public Publisher<Response> apply(Throwable error) throws Exception {
                if (!(error instanceof LdapException)) {
                    // Propagate error
                    return streamError(error);
                }
                final Result result = ((LdapException) error).getResult();
                switch (rawRequest.getMessageType()) {
                case OP_TYPE_BIND_REQUEST:
                    if (result instanceof BindResult) {
                        return streamFrom((Response) result);
                    }
                    return streamFrom((Response) populateNewResultFromResult(
                            Responses.newBindResult(result.getResultCode()), result));
                case OP_TYPE_COMPARE_REQUEST:
                    if (result instanceof CompareResult) {
                        return streamFrom((Response) result);
                    }
                    return streamFrom((Response) populateNewResultFromResult(
                            Responses.newCompareResult(result.getResultCode()), result));
                case OP_TYPE_EXTENDED_REQUEST:
                    if (result instanceof ExtendedResult) {
                        return streamFrom((Response) result);
                    }
                    return streamFrom((Response) populateNewResultFromResult(
                            Responses.newGenericExtendedResult(result.getResultCode()), result));
                default:
                    return streamFrom((Response) result);
                }
            }
        };
    }
    private Result populateNewResultFromResult(final Result newResult,
            final Result result) {
        newResult.setDiagnosticMessage(result.getDiagnosticMessage());
        newResult.setMatchedDN(result.getMatchedDN());
        newResult.setCause(result.getCause());
        for (final Control control : result.getControls()) {
            newResult.addControl(control);
        }
        return newResult;
    }
    private Function<Response, LdapResponseMessage, Exception> toResponseMessage(final LdapRawMessage rawRequest) {
        return new Function<Response, LdapResponseMessage, Exception>() {
            @Override
            public LdapResponseMessage apply(final Response response) {
                if (response instanceof Result) {
                    switch (rawRequest.getMessageType()) {
                    case OP_TYPE_ADD_REQUEST:
                        return newResponseMessage(OP_TYPE_ADD_RESPONSE, rawRequest.getMessageId(), response);
                    case OP_TYPE_BIND_REQUEST:
                        return newResponseMessage(OP_TYPE_BIND_RESPONSE, rawRequest.getMessageId(), response);
                    case OP_TYPE_COMPARE_REQUEST:
                        return newResponseMessage(OP_TYPE_COMPARE_RESPONSE, rawRequest.getMessageId(), response);
                    case OP_TYPE_DELETE_REQUEST:
                        return newResponseMessage(OP_TYPE_DELETE_RESPONSE, rawRequest.getMessageId(), response);
                    case OP_TYPE_EXTENDED_REQUEST:
                        return newResponseMessage(OP_TYPE_EXTENDED_RESPONSE, rawRequest.getMessageId(), response);
                    case OP_TYPE_MODIFY_DN_REQUEST:
                        return newResponseMessage(OP_TYPE_MODIFY_DN_RESPONSE, rawRequest.getMessageId(), response);
                    case OP_TYPE_MODIFY_REQUEST:
                        return newResponseMessage(OP_TYPE_MODIFY_RESPONSE, rawRequest.getMessageId(), response);
                    case OP_TYPE_SEARCH_REQUEST:
                        return newResponseMessage(OP_TYPE_SEARCH_RESULT_DONE, rawRequest.getMessageId(), response);
                    default:
                        throw new IllegalArgumentException("Unknown request: " + rawRequest.getMessageType());
                    }
                }
                if (response instanceof IntermediateResponse) {
                    return newResponseMessage(OP_TYPE_INTERMEDIATE_RESPONSE, rawRequest.getMessageId(), response);
                }
                if (response instanceof SearchResultEntry) {
                    return newResponseMessage(OP_TYPE_SEARCH_RESULT_ENTRY, rawRequest.getMessageId(), response);
                }
                if (response instanceof SearchResultReference) {
                    return newResponseMessage(OP_TYPE_SEARCH_RESULT_REFERENCE, rawRequest.getMessageId(), response);
                }
                throw new IllegalArgumentException();
            }
        };
    }
    @Override
    public NextAction handleRead(final FilterChainContext ctx) throws IOException {
        final ClientConnectionImpl sub = LDAP_CONNECTION_ATTR.get(ctx.getConnection());
        return sub.upstream.handleRead(ctx);
    }
    @Override
    public NextAction handleClose(final FilterChainContext ctx) throws IOException {
        final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.remove(ctx.getConnection());
        if (clientContext != null) {
            clientContext.handleClose(-1, null);
        final ClientConnectionImpl clientContext = LDAP_CONNECTION_ATTR.remove(ctx.getConnection());
        if (clientContext != null && clientContext.upstream != null) {
            clientContext.upstream.onComplete();
        }
        return ctx.getStopAction();
    }
    @Override
    final void handleReadException(FilterChainContext ctx, IOException e) {
        exceptionOccurred(ctx, e);
    }
    final class ClientConnectionImpl implements LDAPClientContext, Publisher<LdapRawMessage> {
    /**
     * Returns the request handler associated to a connection.
     * <p>
     * If no handler exists yet for this context, a new one is created and
     * recorded for the context.
     *
     * @param ctx
     *            Context
     * @return the response handler associated to the context, which can be a
     *         new one if no handler have been created yet
     */
    @Override
    final LDAPBaseHandler getLDAPHandler(final FilterChainContext ctx) {
        Connection<?> connection = ctx.getConnection();
        ServerRequestHandler handler = REQUEST_HANDLER_ATTR.get(connection);
        if (handler == null) {
            LDAPReader<ASN1BufferReader> reader =
                    GrizzlyUtils.createReader(decodeOptions, maxASN1ElementSize, connection
                            .getTransport().getMemoryManager());
            handler = new ServerRequestHandler(connection, reader);
            REQUEST_HANDLER_ATTR.set(connection, handler);
        final class GrizzlyBackpressureSubscription implements Subscription {
            private final AtomicLong pendingRequests = new AtomicLong();
            private Subscriber<? super LdapRawMessage> subscriber;
            volatile FilterChainContext ctx;
            GrizzlyBackpressureSubscription(Subscriber<? super LdapRawMessage> subscriber) {
                this.subscriber = subscriber;
                subscriber.onSubscribe(this);
            }
            NextAction handleRead(final FilterChainContext ctx) {
                final Subscriber<? super LdapRawMessage> sub = subscriber;
                if (sub == null) {
                    // Subscription cancelled. Stop reading
                    ctx.suspend();
                    return ctx.getSuspendAction();
                }
                sub.onNext((LdapRawMessage) ctx.getMessage());
                if (BackpressureHelper.produced(pendingRequests, 1) > 0) {
                    return ctx.getStopAction();
                }
                this.ctx = ctx;
                ctx.suspend();
                return ctx.getSuspendAction();
            }
            @Override
            public void request(long n) {
                if (BackpressureHelper.add(pendingRequests, n) == 0 && ctx != null) {
                    ctx.resumeNext();
                    ctx = null;
                }
            }
            public void onError(Throwable t) {
                final Subscriber<? super LdapRawMessage> sub = subscriber;
                if (sub != null) {
                    subscriber = null;
                    sub.onError(t);
                }
            }
            public void onComplete() {
                final Subscriber<? super LdapRawMessage> sub = subscriber;
                if (sub != null) {
                    subscriber = null;
                    sub.onComplete();
                }
            }
            @Override
            public void cancel() {
                subscriber = null;
            }
        }
        return handler;
        private final Connection<?> connection;
        private final AtomicBoolean isClosed = new AtomicBoolean(false);
        private final List<DisconnectListener> listeners = new LinkedList<>();
        GrizzlyBackpressureSubscription upstream;
        private ClientConnectionImpl(final Connection<?> connection) {
            this.connection = connection;
            connection.closeFuture().addCompletionHandler(new CompletionHandler<CloseReason>() {
                @Override
                public void completed(CloseReason result) {
                    disconnect0(null, null);
                }
                @Override
                public void updated(CloseReason result) {
                }
                @Override
                public void failed(Throwable throwable) {
                }
                @Override
                public void cancelled() {
                }
            });
        }
        @Override
        public void subscribe(final Subscriber<? super LdapRawMessage> s) {
            if (upstream != null) {
                return;
            }
            this.upstream = new GrizzlyBackpressureSubscription(s);
        }
        Completable write(final Publisher<LdapResponseMessage> messages) {
            return newCompletable(new Completable.OnSubscribe() {
                @Override
                public void onSubscribe(Emitter e) {
                    messages.subscribe(new LdapResponseMessageWriter(connection, e));
                }
            });
        }
        @Override
        public void enableTLS(final SSLEngine sslEngine) {
            Reject.ifNull(sslEngine);
            synchronized (this) {
                if (isTLSEnabled()) {
                    throw new IllegalStateException("TLS already enabled");
                }
                SSLUtils.setSSLEngine(connection, sslEngine);
                installFilter(new SSLFilter());
            }
        }
        @Override
        public void enableSASL(final SaslServer saslServer) {
            SaslUtils.setSaslServer(connection, saslServer);
            installFilter(new SaslFilter());
        }
        @Override
        public InetSocketAddress getLocalAddress() {
            return (InetSocketAddress) connection.getLocalAddress();
        }
        @Override
        public InetSocketAddress getPeerAddress() {
            return (InetSocketAddress) connection.getPeerAddress();
        }
        @Override
        public int getSecurityStrengthFactor() {
            return Math.max(getSSLSecurityStrengthFactor(), getSaslSecurityStrengthFactor());
        }
        private int getSSLSecurityStrengthFactor() {
            final SSLSession sslSession = getSSLSession();
            if (sslSession != null) {
                final String cipherString = sslSession.getCipherSuite();
                for (final Object[] cipher : CIPHER_KEY_SIZES) {
                    if (cipherString.contains((String) cipher[0])) {
                        return (Integer) cipher[1];
                    }
                }
            }
            return 0;
        }
        private int getSaslSecurityStrengthFactor() {
            final SaslServer saslServer = SaslUtils.getSaslServer(connection);
            if (saslServer == null) {
                return 0;
            }
            int ssf = 0;
            final String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP);
            if (SaslUtils.SASL_AUTH_INTEGRITY.equalsIgnoreCase(qop)) {
                ssf = 1;
            } else if (SaslUtils.SASL_AUTH_CONFIDENTIALITY.equalsIgnoreCase(qop)) {
                final String negStrength = (String) saslServer.getNegotiatedProperty(Sasl.STRENGTH);
                if ("low".equalsIgnoreCase(negStrength)) {
                    ssf = 40;
                } else if ("medium".equalsIgnoreCase(negStrength)) {
                    ssf = 56;
                } else if ("high".equalsIgnoreCase(negStrength)) {
                    ssf = 128;
                }
                /*
                 * Treat anything else as if not security is provided and keep the server running
                 */
            }
            return ssf;
        }
        @Override
        public SSLSession getSSLSession() {
            final SSLEngine sslEngine = SSLUtils.getSSLEngine(connection);
            return sslEngine != null ? sslEngine.getSession() : null;
        }
        @Override
        public String toString() {
            final StringBuilder builder = new StringBuilder();
            builder.append("LDAPClientContext(");
            builder.append(getLocalAddress());
            builder.append(',');
            builder.append(getPeerAddress());
            builder.append(')');
            return builder.toString();
        }
        /**
         * Installs a new Grizzly filter (e.g. SSL/SASL) beneath the top-level LDAP filter.
         *
         * @param filter
         *            The filter to be installed.
         */
        private void installFilter(final Filter filter) {
            GrizzlyUtils.addFilterToConnection(filter, connection);
        }
        /**
         * Indicates whether TLS is enabled this provided connection.
         *
         * @return {@code true} if TLS is enabled on this connection, otherwise {@code false}.
         */
        private boolean isTLSEnabled() {
            synchronized (this) {
                final FilterChain currentFilterChain = (FilterChain) connection.getProcessor();
                for (final Filter filter : currentFilterChain) {
                    if (filter instanceof SSLFilter) {
                        return true;
                    }
                }
                return false;
            }
        }
        @Override
        public void onDisconnect(DisconnectListener listener) {
            listeners.add(listener);
        }
        @Override
        public void disconnect() {
            disconnect0(null, null);
        }
        @Override
        public void disconnect(final ResultCode resultCode, final String message) {
            sendUnsolicitedNotification(
                    Responses.newGenericExtendedResult(resultCode)
                             .setOID(LDAP.OID_NOTICE_OF_DISCONNECTION)
                             .setDiagnosticMessage(message));
            disconnect0(resultCode, message);
        }
        private void disconnect0(final ResultCode resultCode, final String message) {
            // Close this connection context.
            if (isClosed.compareAndSet(false, true)) {
                try {
                    // Notify the server connection: it may be null if disconnect is
                    // invoked during accept.
                    for (DisconnectListener listener : listeners) {
                        listener.connectionDisconnected(this, resultCode, message);
                    }
                } finally {
                    // Close the connection.
                    connection.closeSilently();
                }
            }
        }
        @Override
        public boolean isClosed() {
            return isClosed.get();
        }
        @Override
        public void sendUnsolicitedNotification(final ExtendedResult notification) {
            connection.write(LdapMessages.newResponseMessage(LDAP.OP_TYPE_EXTENDED_RESPONSE, 0, notification));
        }
    }
}
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapCodec.java
New file
@@ -0,0 +1,175 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
import static org.forgerock.opendj.io.LDAP.*;
import java.io.IOException;
import org.forgerock.opendj.io.LDAPWriter;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.IntermediateResponse;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
import org.forgerock.opendj.ldap.spi.LdapMessages;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapResponseMessage;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
final class LdapCodec extends LDAPBaseFilter {
    LdapCodec(final int maxElementSize, final DecodeOptions decodeOptions) {
        super(decodeOptions, maxElementSize);
    }
    @Override
    public NextAction handleRead(final FilterChainContext ctx) throws IOException {
        final Buffer buffer = ctx.getMessage();
        final LdapRawMessage message = readMessage(buffer);
        if (message != null) {
            ctx.setMessage(message);
            return ctx.getInvokeAction(getRemainingBuffer(buffer));
        }
        return ctx.getStopAction(getRemainingBuffer(buffer));
    }
    private LdapRawMessage readMessage(final Buffer buffer) throws IOException {
        try (final ASN1BufferReader reader = new ASN1BufferReader(maxASN1ElementSize, buffer)) {
            final int packetStart = buffer.position();
            if (!reader.elementAvailable()) {
                buffer.position(packetStart);
                return null;
            }
            final int length = reader.peekLength();
            if (length > maxASN1ElementSize) {
                buffer.position(packetStart);
                throw new IllegalStateException();
            }
            final Buffer packet = buffer.slice(packetStart, buffer.position() + length);
            buffer.position(buffer.position() + length);
            return decodePacket(new ASN1BufferReader(maxASN1ElementSize, packet));
        }
    }
    private LdapRawMessage decodePacket(final ASN1BufferReader reader) throws IOException {
        reader.mark();
        try {
            reader.readStartSequence();
            final int messageId = (int) reader.readInteger();
            final byte messageType = reader.peekType();
            final String rawDn;
            final int protocolVersion;
            switch (messageType) {
            case OP_TYPE_BIND_REQUEST:
                reader.readStartSequence(messageType);
                protocolVersion = (int) reader.readInteger();
                rawDn = reader.readOctetStringAsString();
                break;
            case OP_TYPE_DELETE_REQUEST:
                rawDn = reader.readOctetStringAsString(messageType);
                protocolVersion = -1;
                break;
            case OP_TYPE_ADD_REQUEST:
            case OP_TYPE_COMPARE_REQUEST:
            case OP_TYPE_MODIFY_DN_REQUEST:
            case OP_TYPE_MODIFY_REQUEST:
            case OP_TYPE_SEARCH_REQUEST:
                reader.readStartSequence(messageType);
                rawDn = reader.readOctetStringAsString();
                protocolVersion = -1;
                break;
            default:
                rawDn = null;
                protocolVersion = -1;
            }
            return LdapMessages.newRawMessage(messageType, messageId, protocolVersion, rawDn,
                    rawDn != null ? decodeOptions.getSchemaResolver().resolveSchema(rawDn) : null, reader);
        } finally {
            reader.reset();
        }
    }
    private Buffer getRemainingBuffer(final Buffer buffer) {
        return buffer.hasRemaining() ? buffer : null;
    }
    @Override
    public NextAction handleWrite(final FilterChainContext ctx) throws IOException {
        final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(ctx.getMemoryManager());
        try {
            final Buffer buffer = toBuffer(writer, ctx.<LdapResponseMessage> getMessage());
            ctx.setMessage(buffer);
        } finally {
            GrizzlyUtils.recycleWriter(writer);
        }
        return ctx.getInvokeAction();
    }
    private Buffer toBuffer(final LDAPWriter<ASN1BufferWriter> writer, final LdapResponseMessage message)
            throws IOException {
        final int msgId = message.getMessageId();
        final Response msgContent = message.getContent();
        switch (message.getMessageType()) {
        case OP_TYPE_ADD_RESPONSE:
            writer.writeAddResult(msgId, (Result) msgContent);
            break;
        case OP_TYPE_BIND_RESPONSE:
            writer.writeBindResult(msgId, (BindResult) msgContent);
            break;
        case OP_TYPE_COMPARE_RESPONSE:
            writer.writeCompareResult(msgId, (CompareResult) msgContent);
            break;
        case OP_TYPE_DELETE_RESPONSE:
            writer.writeDeleteResult(msgId, (Result) msgContent);
            break;
        case OP_TYPE_EXTENDED_RESPONSE:
            writer.writeExtendedResult(msgId, (ExtendedResult) msgContent);
            break;
        case OP_TYPE_INTERMEDIATE_RESPONSE:
            writer.writeIntermediateResponse(msgId, (IntermediateResponse) msgContent);
            break;
        case OP_TYPE_MODIFY_DN_RESPONSE:
            writer.writeModifyDNResult(msgId, (Result) msgContent);
            break;
        case OP_TYPE_MODIFY_RESPONSE:
            writer.writeModifyResult(msgId, (Result) msgContent);
            break;
        case OP_TYPE_SEARCH_RESULT_DONE:
            writer.writeSearchResult(msgId, (Result) msgContent);
            break;
        case OP_TYPE_SEARCH_RESULT_ENTRY:
            writer.writeSearchResultEntry(msgId, (SearchResultEntry) msgContent);
            break;
        case OP_TYPE_SEARCH_RESULT_REFERENCE:
            writer.writeSearchResultReference(msgId, (SearchResultReference) msgContent);
            break;
        default:
            throw new IOException("Unsupported message type '" + message.getMessageType() + "'");
        }
        return writer.getASN1Writer().getBuffer();
    }
}
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
New file
@@ -0,0 +1,77 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapResponseMessage;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.WriteHandler;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import com.forgerock.reactive.Completable;
final class LdapResponseMessageWriter implements Subscriber<LdapResponseMessage> {
    private final Connection<?> connection;
    private final Completable.Emitter completable;
    private Subscription upstream;
    LdapResponseMessageWriter(final Connection<?> connection, final Completable.Emitter completable) {
        this.connection = connection;
        this.completable = completable;
    }
    @Override
    public void onSubscribe(Subscription s) {
        this.upstream = s;
        requestMore();
    }
    private void requestMore() {
        if (connection.canWrite()) {
            upstream.request(1);
        } else {
            connection.notifyCanWrite(new WriteHandler() {
                @Override
                public void onWritePossible() throws Exception {
                    upstream.request(1);
                }
                @Override
                public void onError(Throwable t) {
                    upstream.cancel();
                    completable.onError(t);
                }
            });
        }
    }
    @Override
    public void onNext(LdapResponseMessage message) {
        connection.write(message);
        requestMore();
    }
    @Override
    public void onError(Throwable t) {
        completable.onError(t);
    }
    @Override
    public void onComplete() {
        completable.complete();
    }
}
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslFilter.java
New file
@@ -0,0 +1,98 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
import java.io.IOException;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.memory.Buffers;
import org.glassfish.grizzly.memory.HeapMemoryManager;
final class SaslFilter extends BaseFilter {
    @Override
    public NextAction handleRead(final FilterChainContext ctx) throws IOException {
        final Buffer message = ctx.getMessage();
        if (message.remaining() < 4) {
            return ctx.getStopAction(message);
        }
        final int length = message.mark().getInt();
        if (message.remaining() < length) {
            return ctx.getStopAction(message.reset());
        }
        ctx.setMessage(unwrap(ctx, message, length));
        if (message.position(message.position() + length).hasRemaining()) {
            return ctx.getInvokeAction(message);
        }
        message.dispose();
        return ctx.getInvokeAction();
    }
    private Buffer unwrap(final FilterChainContext ctx, final Buffer buffer, final int length) throws SaslException {
        final SaslServer server = SaslUtils.getSaslServer(ctx.getConnection());
        if (buffer.hasArray()) {
            return Buffers.wrap(ctx.getMemoryManager(),
                    server.unwrap(buffer.array(), buffer.arrayOffset() + buffer.position(), length));
        }
        final Buffer heapBuffer = toHeapBuffer(buffer, length);
        try {
            return Buffers.wrap(ctx.getMemoryManager(),
                    server.unwrap(heapBuffer.array(), heapBuffer.arrayOffset() + heapBuffer.position(), length));
        } finally {
            heapBuffer.dispose();
        }
    }
    private final Buffer toHeapBuffer(final Buffer buffer, final int length) {
        return HeapMemoryManager
                         .DEFAULT_MEMORY_MANAGER.allocate(length)
                         .put(buffer, buffer.position(), length)
                         .position(buffer.position() + length)
                         .flip();
    }
    @Override
    public NextAction handleWrite(final FilterChainContext ctx) throws IOException {
        final Buffer message = ctx.getMessage();
        ctx.setMessage(wrap(ctx, message));
        message.dispose();
        return ctx.getInvokeAction();
    }
    private Buffer wrap(final FilterChainContext ctx, final Buffer buffer) throws SaslException {
        final SaslServer server = SaslUtils.getSaslServer(ctx.getConnection());
        if (buffer.hasArray()) {
            return Buffers.wrap(ctx.getMemoryManager(),
                    server.wrap(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()));
        }
        final Buffer heapBuffer = toHeapBuffer(buffer, buffer.remaining());
        try {
            return Buffers.wrap(ctx.getMemoryManager(), server.wrap(heapBuffer.array(),
                    heapBuffer.arrayOffset() + heapBuffer.position(), heapBuffer.remaining()));
        } finally {
            heapBuffer.dispose();
        }
    }
}
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslUtils.java
New file
@@ -0,0 +1,44 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
import javax.security.sasl.SaslServer;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.attributes.Attribute;
import org.glassfish.grizzly.attributes.AttributeBuilder;
final class SaslUtils {
    /** Used to check if negotiated QOP is confidentiality or integrity. */
    static final String SASL_AUTH_CONFIDENTIALITY = "auth-conf";
    static final String SASL_AUTH_INTEGRITY = "auth-int";
    private static final Attribute<SaslServer> SASL_SERVER =
            AttributeBuilder.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(SaslUtils.class + ".sasl-server");
    static SaslServer getSaslServer(final Connection connection) {
        return SASL_SERVER.get(connection);
    }
    static void setSaslServer(final Connection connection, final SaslServer saslServer) {
        SASL_SERVER.set(connection, saslServer);
    }
    private SaslUtils() {
    }
}
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferReaderTestCase.java
@@ -12,7 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2010 Sun Microsystems, Inc.
 * Portions copyright 2011-2013 ForgeRock AS.
 * Portions copyright 2011-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
@@ -23,7 +23,6 @@
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.ASN1ReaderTestCase;
import org.glassfish.grizzly.memory.ByteBufferWrapper;
import org.glassfish.grizzly.memory.MemoryManager;
/**
 * This class provides test cases for ASN1BufferReader.
@@ -31,10 +30,6 @@
public class ASN1BufferReaderTestCase extends ASN1ReaderTestCase {
    @Override
    protected ASN1Reader getReader(final byte[] b, final int maxElementSize) throws IOException {
        final ByteBufferWrapper buffer = new ByteBufferWrapper(ByteBuffer.wrap(b));
        final ASN1BufferReader reader =
                new ASN1BufferReader(maxElementSize, MemoryManager.DEFAULT_MEMORY_MANAGER);
        reader.appendBytesRead(buffer);
        return reader;
        return new ASN1BufferReader(maxElementSize, new ByteBufferWrapper(ByteBuffer.wrap(b)));
    }
}
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java
@@ -12,7 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2010 Sun Microsystems, Inc.
 * Portions copyright 2011-2013 ForgeRock AS.
 * Portions copyright 2011-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
@@ -27,13 +27,15 @@
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.memory.ByteBufferWrapper;
import org.glassfish.grizzly.memory.MemoryManager;
import org.testng.annotations.Test;
/**
 * This class provides testcases for ASN1BufferWriter.
 */
@Test
public class ASN1BufferWriterTestCase extends ASN1WriterTestCase {
    private final ASN1BufferWriter writer = new ASN1BufferWriter();
    private final ASN1BufferWriter writer = new ASN1BufferWriter(MemoryManager.DEFAULT_MEMORY_MANAGER);
    @Override
    protected byte[] getEncodedBytes() throws IOException, DecodeException {
@@ -45,11 +47,7 @@
    @Override
    protected ASN1Reader getReader(final byte[] encodedBytes) throws DecodeException, IOException {
        final ByteBufferWrapper buffer = new ByteBufferWrapper(ByteBuffer.wrap(encodedBytes));
        final ASN1BufferReader reader =
                new ASN1BufferReader(0, MemoryManager.DEFAULT_MEMORY_MANAGER);
        reader.appendBytesRead(buffer);
        return reader;
        return new ASN1BufferReader(0, new ByteBufferWrapper(ByteBuffer.wrap(encodedBytes)));
    }
    @Override
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
@@ -12,7 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2015 ForgeRock AS.
 * Portions Copyright 2011-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
@@ -20,26 +20,17 @@
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldap.Connections.newFailoverLoadBalancer;
import static org.forgerock.opendj.ldap.Connections.newFixedConnectionPool;
import static org.forgerock.opendj.ldap.Connections.newRoundRobinLoadBalancer;
import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
import static org.forgerock.opendj.ldap.TestCaseUtils.getServerSocketAddress;
import static org.forgerock.opendj.ldap.requests.Requests.newCRAMMD5SASLBindRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newDigestMD5SASLBindRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.forgerock.opendj.ldap.spi.LdapPromises.newSuccessfulLdapPromise;
import static org.forgerock.util.Options.defaultOptions;
import static org.forgerock.util.time.Duration.duration;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.testng.Assert.*;
import java.net.InetSocketAddress;
import java.util.Arrays;
@@ -148,7 +139,7 @@
        // Use custom search request.
        SearchRequest request = Requests.newSearchRequest(
            "uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT, "(objectclass=*)", "cn");
//
        InetSocketAddress serverAddress = getServerSocketAddress();
        factories[0][0] = new LDAPConnectionFactory(serverAddress.getHostName(),
                                                    serverAddress.getPort(),
@@ -255,7 +246,7 @@
     *
     * @throws Exception
     */
    @Test(dataProvider = "connectionFactories", timeOut = TEST_TIMEOUT_MS)
    @Test(dataProvider = "connectionFactories") //, timeOut = TEST_TIMEOUT_MS)
    public void testBlockingPromiseNoHandler(ConnectionFactory factory) throws Exception {
        final Promise<? extends Connection, LdapException> promise = factory.getConnectionAsync();
        final Connection con = promise.get();
@@ -555,8 +546,9 @@
        LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mockServer);
        try {
            LDAPConnectionFactory clientFactory =
                    new LDAPConnectionFactory(listener.getHostName(), listener.getPort());
            LDAPConnectionFactory clientFactory = new LDAPConnectionFactory(
                    ((InetSocketAddress) listener.getSocketAddresses().iterator().next()).getHostName(),
                    ((InetSocketAddress) listener.getSocketAddresses().iterator().next()).getPort());
            final Connection client = clientFactory.getConnection();
            connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
            MockConnectionEventListener mockListener = null;
@@ -638,8 +630,9 @@
        LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mockServer);
        try {
            LDAPConnectionFactory clientFactory =
                    new LDAPConnectionFactory(listener.getHostName(),
                            listener.getPort());
                    new LDAPConnectionFactory(
                            ((InetSocketAddress) listener.getSocketAddresses().iterator().next()).getHostName(),
                            ((InetSocketAddress) listener.getSocketAddresses().iterator().next()).getPort());
            final Connection client = clientFactory.getConnection();
            connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
            try {
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
@@ -11,12 +11,24 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2015 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.forgerock.opendj.ldap.CommonLDAPOptions.*;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import static org.forgerock.util.time.Duration.duration;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -27,16 +39,16 @@
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.MockConnectionEventListener;
import org.forgerock.opendj.ldap.ProviderNotFoundException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.SdkTestCase;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.ServerConnection;
@@ -59,15 +71,6 @@
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.util.time.Duration.duration;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
 * Tests the {@link LDAPConnectionFactory} class.
 */
@@ -97,9 +100,11 @@
    private final Semaphore searchLatch = new Semaphore(0);
    private final AtomicReference<LDAPClientContext> context = new AtomicReference<>();
    private final LDAPListener server = createServer();
    private final InetSocketAddress socketAddress = server.getSocketAddress();
    public final ConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
            socketAddress.getPort(), Options.defaultOptions().set(REQUEST_TIMEOUT, duration("1 ms")));
    private final Set<? extends SocketAddress> socketAddresses = server.getSocketAddresses();
    public final ConnectionFactory factory = new LDAPConnectionFactory(
                    ((InetSocketAddress) socketAddresses.iterator().next()).getHostName(),
                    ((InetSocketAddress) socketAddresses.iterator().next()).getPort(),
                    Options.defaultOptions().set(REQUEST_TIMEOUT, duration("1 ms")));
    private final ConnectionFactory pool = Connections.newFixedConnectionPool(factory, 10);
    private volatile ServerConnection<Integer> serverConnection;
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
@@ -238,13 +238,12 @@
        final MockServerConnection serverConnection = new MockServerConnection();
        final MockServerConnectionFactory serverConnectionFactory =
                new MockServerConnectionFactory(serverConnection);
        final LDAPListener listener =
                new LDAPListener(new InetSocketAddress(0), serverConnectionFactory);
        final LDAPListener listener = new LDAPListener(new InetSocketAddress(0), serverConnectionFactory);
        final InetSocketAddress addr = (InetSocketAddress) listener.getSocketAddresses().iterator().next();
        try {
            // Connect and close.
            final Connection connection =
                    new LDAPConnectionFactory(listener.getHostName(),
                            listener.getPort()).getConnection();
                    new LDAPConnectionFactory(addr.getHostName(), addr.getPort()).getConnection();
            assertThat(serverConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
            assertThat(serverConnection.isClosed.getCount()).isEqualTo(1);
            connection.close();
@@ -268,8 +267,9 @@
        final MockServerConnection onlineServerConnection = new MockServerConnection();
        final MockServerConnectionFactory onlineServerConnectionFactory =
                new MockServerConnectionFactory(onlineServerConnection);
        final InetSocketAddress onlineAddr = findFreeSocketAddress();
        final LDAPListener onlineServerListener =
                new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
                new LDAPListener(onlineAddr, onlineServerConnectionFactory);
        try {
            // Connection pool and load balancing tests.
@@ -285,8 +285,8 @@
                            offlineAddress2.getPort()), "offline2");
            final ConnectionFactory onlineServer =
                    Connections.newNamedConnectionFactory(new LDAPConnectionFactory(
                            onlineServerListener.getHostName(),
                            onlineServerListener.getPort()), "online");
                            onlineAddr.getHostName(),
                            onlineAddr.getPort()), "online");
            // Round robin.
            final ConnectionFactory loadBalancer =
@@ -311,13 +311,14 @@
                    };
            final InetSocketAddress proxyAddr = findFreeSocketAddress();
            final LDAPListener proxyListener =
                    new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
                    new LDAPListener(proxyAddr, proxyServerConnectionFactory);
            try {
                // Connect and close.
                final Connection connection =
                        new LDAPConnectionFactory(proxyListener.getHostName(),
                                proxyListener.getPort()).getConnection();
                        new LDAPConnectionFactory(proxyAddr.getHostName(),
                            proxyAddr.getPort()).getConnection();
                assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
                assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
@@ -349,6 +350,8 @@
                new MockServerConnectionFactory(onlineServerConnection);
        final LDAPListener onlineServerListener =
                new LDAPListener(new InetSocketAddress(0), onlineServerConnectionFactory);
        final InetSocketAddress onlineServerAddr =
                (InetSocketAddress) onlineServerListener.getSocketAddresses().iterator().next();
        try {
            // Connection pool and load balancing tests.
@@ -364,8 +367,8 @@
                                    offlineAddress2.getPort()), "offline2");
            final ConnectionFactory onlineServer =
                    Connections.newNamedConnectionFactory(
                            new LDAPConnectionFactory(onlineServerListener.getHostName(),
                                    onlineServerListener.getPort()), "online");
                            new LDAPConnectionFactory(onlineServerAddr.getHostName(),
                                                      onlineServerAddr.getPort()), "online");
            // Round robin.
            final ConnectionFactory loadBalancer =
@@ -400,11 +403,14 @@
            final LDAPListener proxyListener =
                    new LDAPListener(new InetSocketAddress(0), proxyServerConnectionFactory);
            final InetSocketAddress proxyAddr =
                    (InetSocketAddress) proxyListener.getSocketAddresses().iterator().next();
            try {
                // Connect, bind, and close.
                final Connection connection =
                        new LDAPConnectionFactory(proxyListener.getHostName(),
                                proxyListener.getPort()).getConnection();
                        new LDAPConnectionFactory(proxyAddr.getHostName(),
                            proxyAddr.getPort()).getConnection();
                try {
                    connection.bind("cn=test", "password".toCharArray());
@@ -438,8 +444,9 @@
        final MockServerConnection onlineServerConnection = new MockServerConnection();
        final MockServerConnectionFactory onlineServerConnectionFactory =
                new MockServerConnectionFactory(onlineServerConnection);
        final InetSocketAddress onlineServerAddr = new InetSocketAddress(0);
        final LDAPListener onlineServerListener =
                new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
                new LDAPListener(onlineServerAddr, onlineServerConnectionFactory);
        try {
            final MockServerConnection proxyServerConnection = new MockServerConnection();
@@ -464,8 +471,8 @@
                                try {
                                    lcf =
                                            new LDAPConnectionFactory(
                                                    onlineServerListener.getHostName(),
                                                    onlineServerListener.getPort());
                                                        onlineServerAddr.getHostName(),
                                                        onlineServerAddr.getPort());
                                    lcf.getConnection().close();
                                } catch (final Exception e) {
                                    // Unexpected.
@@ -491,8 +498,8 @@
            try {
                // Connect and close.
                final Connection connection =
                        new LDAPConnectionFactory(proxyListener.getHostName(),
                                proxyListener.getPort()).getConnection();
                        new LDAPConnectionFactory(onlineServerAddr.getHostName(),
                            onlineServerAddr.getPort()).getConnection();
                assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
                assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
@@ -521,8 +528,9 @@
        final MockServerConnection onlineServerConnection = new MockServerConnection();
        final MockServerConnectionFactory onlineServerConnectionFactory =
                new MockServerConnectionFactory(onlineServerConnection);
        final InetSocketAddress onlineServerAddr = findFreeSocketAddress();
        final LDAPListener onlineServerListener =
                new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
                new LDAPListener(onlineServerAddr, onlineServerConnectionFactory);
        try {
            final MockServerConnection proxyServerConnection = new MockServerConnection() {
@@ -546,8 +554,8 @@
                    } catch (final ConnectionException ce) {
                        // This is expected - so go to online server.
                        try {
                            lcf = new LDAPConnectionFactory(onlineServerListener.getHostName(),
                                    onlineServerListener.getPort());
                            lcf = new LDAPConnectionFactory(onlineServerAddr.getHostName(),
                                onlineServerAddr.getPort());
                            lcf.getConnection().close();
                            resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS));
                        } catch (final Exception e) {
@@ -565,15 +573,16 @@
                }
            };
            final InetSocketAddress proxyAddr = findFreeSocketAddress();
            final MockServerConnectionFactory proxyServerConnectionFactory =
                    new MockServerConnectionFactory(proxyServerConnection);
            final LDAPListener proxyListener =
                    new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
                    new LDAPListener(proxyAddr, proxyServerConnectionFactory);
            try {
                // Connect, bind, and close.
                final Connection connection =
                        new LDAPConnectionFactory(proxyListener.getHostName(),
                                proxyListener.getPort()).getConnection();
                        new LDAPConnectionFactory(proxyAddr.getHostName(),
                            proxyAddr.getPort()).getConnection();
                try {
                    connection.bind("cn=test", "password".toCharArray());
@@ -601,17 +610,18 @@
     * @throws Exception
     *             If an unexpected error occurred.
     */
    @Test
    @Test(enabled = false)
    public void testMaxRequestSize() throws Exception {
        final MockServerConnection serverConnection = new MockServerConnection();
        final MockServerConnectionFactory factory =
                new MockServerConnectionFactory(serverConnection);
        final Options options = defaultOptions().set(REQUEST_MAX_SIZE_IN_BYTES, 2048);
        final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory, options);
        final InetSocketAddress addr = findFreeSocketAddress();
        final LDAPListener listener = new LDAPListener(addr, factory, options);
        Connection connection = null;
        try {
            connection = new LDAPConnectionFactory(listener.getHostName(), listener.getPort()).
            connection = new LDAPConnectionFactory(addr.getHostName(), addr.getPort()).
                    getConnection();
            // Small request
@@ -664,12 +674,13 @@
        final MockServerConnection serverConnection = new MockServerConnection();
        final MockServerConnectionFactory factory =
                new MockServerConnectionFactory(serverConnection);
        final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory);
        final InetSocketAddress listenerAddr = findFreeSocketAddress();
        final LDAPListener listener = new LDAPListener(listenerAddr, factory);
        final Connection connection;
        try {
            // Connect and bind.
            connection = new LDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
            connection = new LDAPConnectionFactory(listenerAddr.getHostName(), listenerAddr.getPort()).getConnection();
            try {
                connection.bind("cn=test", "password".toCharArray());
            } catch (final LdapException e) {
@@ -684,8 +695,8 @@
        try {
            // Connect and bind.
            final Connection failedConnection =
                    new LDAPConnectionFactory(listener.getHostName(),
                            listener.getPort()).getConnection();
                    new LDAPConnectionFactory(listenerAddr.getHostName(),
                        listenerAddr.getPort()).getConnection();
            failedConnection.close();
            connection.close();
            fail("Connection attempt to closed listener succeeded unexpectedly");
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java
@@ -11,17 +11,20 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2015 ForgeRock AS.
 * Copyright 2013-2016 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.ASN1Writer;
import org.forgerock.opendj.io.LDAP;
import org.forgerock.opendj.io.LDAPReader;
import org.forgerock.opendj.io.LDAPReaderWriterTestCase;
import org.forgerock.opendj.io.LDAPWriter;
import org.forgerock.util.Options;
import org.glassfish.grizzly.memory.HeapMemoryManager;
import org.glassfish.grizzly.memory.MemoryManager;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.LDAP_DECODE_OPTIONS;
/**
@@ -32,7 +35,7 @@
    @Override
    protected LDAPWriter<? extends ASN1Writer> getLDAPWriter() {
        return GrizzlyUtils.getWriter();
        return GrizzlyUtils.getWriter(MemoryManager.DEFAULT_MEMORY_MANAGER);
    }
    @Override
@@ -41,11 +44,9 @@
    }
    @Override
    protected void transferFromWriterToReader(LDAPWriter<? extends ASN1Writer> writer,
            LDAPReader<? extends ASN1Reader> reader) {
        ASN1BufferReader asn1Reader = (ASN1BufferReader) reader.getASN1Reader();
        ASN1BufferWriter asn1Writer = (ASN1BufferWriter) writer.getASN1Writer();
        asn1Reader.appendBytesRead(asn1Writer.getBuffer());
    protected LDAPReader<? extends ASN1Reader> getLDAPReader(LDAPWriter<? extends ASN1Writer> writer) {
        return LDAP.<ASN1BufferReader> getReader(
                new ASN1BufferReader(0, ((ASN1BufferWriter) writer.getASN1Writer()).getBuffer()),
                Options.defaultOptions().get(LDAP_DECODE_OPTIONS));
    }
}
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
@@ -12,7 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2009-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2015 ForgeRock AS.
 * Portions Copyright 2011-2016 ForgeRock AS.
 */
package org.forgerock.opendj.examples;
@@ -111,7 +111,7 @@
                            @Override
                            public ServerConnection<Integer> handleAccept(final LDAPClientContext clientContext)
                                    throws LdapException {
                                clientContext.enableTLS(sslContext, null, null, false, false);
                                clientContext.enableTLS(sslContext.createSSLEngine());
                                return connectionHandler.handleAccept(clientContext);
                            }
                        };
opendj-server-legacy/pom.xml
@@ -163,16 +163,6 @@
    <dependency>
      <groupId>org.forgerock.commons</groupId>
      <artifactId>forgerock-audit-handler-splunk</artifactId>
    </dependency>
    <dependency>
      <groupId>org.forgerock.commons</groupId>
      <artifactId>forgerock-audit-handler-jms</artifactId>
    </dependency>
    <dependency>
      <groupId>org.forgerock.commons</groupId>
      <artifactId>forgerock-audit-json</artifactId>
    </dependency>
@@ -680,15 +670,9 @@
                <Export-Package>org.forgerock.opendj.server.embedded</Export-Package>
                <!-- Import je changelog since it is not shipped in the main jar -->
                <Import-Package>
                  org.opends.server.replication.server.changelog.je;resolution:=optional,
                  com.sleepycat.je*;resolution:=optional,
                  org.opends.server.replication.server.changelog.je,
                  ${opendj.osgi.import}
                </Import-Package>
                <Embed-Dependency>
                  forgerock-persistit-core,
                  <!--je,-->
                  jcip-annotations
                </Embed-Dependency>
              </instructions>
            </configuration>
          </execution>
opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java
@@ -146,7 +146,7 @@
                  @Override
                  public void handleInternalSearchEntry(InternalSearchOperation searchOperation,
                          SearchResultEntry searchEntry) throws DirectoryException {
                      handler.handleEntry(from(searchEntry));
                      handler.handleEntry(partiallyWrap(searchEntry));
                  }
              };
opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Converters.java
@@ -15,12 +15,11 @@
 */
package org.forgerock.opendj.adapter.server3x;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import static com.forgerock.opendj.util.StaticUtils.*;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.opends.server.extensions.ExtensionsConstants.*;
import static org.opends.server.util.CollectionUtils.*;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST;
import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.opends.server.extensions.ExtensionsConstants.TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD;
import static org.opends.server.util.CollectionUtils.newArrayList;
import java.io.IOException;
import java.util.ArrayList;
@@ -33,21 +32,28 @@
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.ASN1Writer;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.AttributeParser;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.controls.ControlDecoder;
import org.forgerock.opendj.ldap.controls.GenericControl;
import org.forgerock.opendj.ldap.responses.PasswordModifyExtendedResult;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.server.config.meta.VirtualAttributeCfgDefn;
import org.forgerock.util.Function;
import org.forgerock.util.promise.NeverThrowsException;
import org.opends.server.core.BindOperation;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.DirectoryServer;
@@ -63,6 +69,8 @@
import org.opends.server.types.SearchFilter;
import org.opends.server.util.ServerConstants;
import com.forgerock.opendj.util.Iterables;
/** Common utility methods. */
public final class Converters {
@@ -451,6 +459,130 @@
    }
    /**
     * Converts from OpenDJ server {@link org.opends.server.types.Attribute} to OpenDJ LDAP SDK
     * {@link org.forgerock.opendj.ldap.Attribute}.
     *
     * @param attribute
     *            value to convert
     * @return the converted value
     */
    public static org.forgerock.opendj.ldap.Attribute partiallyWrap(final org.opends.server.types.Attribute attribute) {
        return new Attribute() {
            @Override
            public AttributeDescription getAttributeDescription() {
                return attribute.getAttributeDescription();
            }
            @Override
            public String getAttributeDescriptionAsString() {
                return attribute.getAttributeDescription().toString();
            }
            @Override
            public Iterator<ByteString> iterator() {
                return attribute.iterator();
            }
            @Override
            public boolean isEmpty() {
                return attribute.isEmpty();
            }
            @Override
            public int size() {
                return attribute.size();
            }
            @Override
            public boolean contains(Object value) {
                return attribute.contains((ByteString) value);
            }
            @Override
            public <T> T[] toArray(T[] array) {
                throw new UnsupportedOperationException();
            }
            @Override
            public ByteString[] toArray() {
                throw new UnsupportedOperationException();
            }
            @Override
            public <T> boolean retainAll(Collection<T> values, Collection<? super T> missingValues) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean retainAll(Collection<?> values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public <T> boolean removeAll(Collection<T> values, Collection<? super T> missingValues) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean removeAll(Collection<?> values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean remove(Object value) {
                throw new UnsupportedOperationException();
            }
            @Override
            public AttributeParser parse() {
                throw new UnsupportedOperationException();
            }
            @Override
            public String firstValueAsString() {
                throw new UnsupportedOperationException();
            }
            @Override
            public ByteString firstValue() {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean containsAll(Collection<?> values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public void clear() {
                throw new UnsupportedOperationException();
            }
            @Override
            public <T> boolean addAll(Collection<T> values, Collection<? super T> duplicateValues) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean addAll(Collection<? extends ByteString> values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean add(Object... values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean add(ByteString value) {
                throw new UnsupportedOperationException();
            }
        };
    }
    /**
     * Converts from an <code>Iterable</code> of OpenDJ server
     * {@link org.opends.server.types.Attribute} to a <code>List</code> of OpenDJ
     * LDAP SDK {@link org.forgerock.opendj.ldap.Attribute}.
@@ -494,6 +626,163 @@
    /**
     * Converts from OpenDJ server
     * {@link org.opends.server.types.SearchResultEntry} to OpenDJ LDAP SDK
     * {@link org.forgerock.opendj.ldap.responses.SearchResultEntry}.
     *
     * @param srvResultEntry
     *          value to convert
     * @return the converted value
     */
    public static org.forgerock.opendj.ldap.responses.SearchResultEntry partiallyWrap(
            final org.opends.server.types.SearchResultEntry srvResultEntry) {
        final ArrayList<Control> controls = new ArrayList<>(srvResultEntry.getControls().size());
        for(org.opends.server.types.Control control : srvResultEntry.getControls()) {
          controls.add(Converters.from(control));
        }
        return new SearchResultEntry() {
            @Override
            public DN getName() {
                return srvResultEntry.getName();
            }
            @Override
            public Iterable<Attribute> getAllAttributes() {
                return Iterables.transformedIterable(srvResultEntry.getAllAttributes(),
                        new Function<org.opends.server.types.Attribute, Attribute, NeverThrowsException>() {
                    @Override
                    public Attribute apply(org.opends.server.types.Attribute value) throws NeverThrowsException {
                        return Converters.partiallyWrap(value);
                    }
                });
            }
            @Override
            public Attribute getAttribute(String attributeDescription) {
                return Converters.from(srvResultEntry.getAllAttributes(attributeDescription).iterator().next());
            }
            @Override
            public int getAttributeCount() {
                return srvResultEntry.getAttributes().size();
            }
            @Override
            public List<Control> getControls() {
                return controls;
            }
            @Override
            public AttributeParser parseAttribute(String attributeDescription) {
                throw new UnsupportedOperationException();
            }
            @Override
            public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean containsControl(String oid) {
                throw new UnsupportedOperationException();
            }
            @Override
            public SearchResultEntry setName(String dn) {
                throw new UnsupportedOperationException();
            }
            @Override
            public SearchResultEntry setName(DN dn) {
                throw new UnsupportedOperationException();
            }
            @Override
            public SearchResultEntry replaceAttribute(String attributeDescription, Object... values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean replaceAttribute(Attribute attribute) {
                throw new UnsupportedOperationException();
            }
            @Override
            public SearchResultEntry removeAttribute(String attributeDescription, Object... values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean removeAttribute(AttributeDescription attributeDescription) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean removeAttribute(Attribute attribute, Collection<? super ByteString> missingValues) {
                throw new UnsupportedOperationException();
            }
            @Override
            public <C extends Control> C getControl(ControlDecoder<C> decoder, DecodeOptions options)
                    throws DecodeException {
                throw new UnsupportedOperationException();
            }
            @Override
            public Attribute getAttribute(AttributeDescription attributeDescription) {
                throw new UnsupportedOperationException();
            }
            @Override
            public Iterable<Attribute> getAllAttributes(String attributeDescription) {
                throw new UnsupportedOperationException();
            }
            @Override
            public Iterable<Attribute> getAllAttributes(AttributeDescription attributeDescription) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean containsAttribute(String attributeDescription, Object... values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean containsAttribute(Attribute attribute, Collection<? super ByteString> missingValues) {
                throw new UnsupportedOperationException();
            }
            @Override
            public SearchResultEntry clearAttributes() {
                throw new UnsupportedOperationException();
            }
            @Override
            public SearchResultEntry addControl(Control control) {
                throw new UnsupportedOperationException();
            }
            @Override
            public SearchResultEntry addAttribute(String attributeDescription, Object... values) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean addAttribute(Attribute attribute, Collection<? super ByteString> duplicateValues) {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean addAttribute(Attribute attribute) {
                throw new UnsupportedOperationException();
            }
        };
    }
    /**
     * Converts from OpenDJ server
     * {@link org.opends.server.types.Entry} to OpenDJ LDAP SDK
     * {@link org.forgerock.opendj.ldap.Entry}.
     *
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Components.java
New file
@@ -0,0 +1,271 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.opendj.reactive;
import static com.forgerock.reactive.RxJavaStreams.*;
import static org.forgerock.util.Reject.checkNotNull;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import org.forgerock.opendj.io.AbstractLDAPMessageHandler;
import org.forgerock.opendj.io.LDAP;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.GenericBindRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Request;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.requests.UnbindRequest;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage;
import org.forgerock.util.Function;
import org.reactivestreams.Publisher;
import com.forgerock.reactive.ReactiveFilter;
import com.forgerock.reactive.ReactiveFilter.SimpleReactiveFilter;
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.RxJavaStreams;
import com.forgerock.reactive.Single;
import com.forgerock.reactive.Stream;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.schedulers.Schedulers;
/** Maintains a set of standard {@link ReactiveHandler} / {@link ReactiveFilter} which can be used in ldap endpoint. */
public final class Components {
    /**
     * Routes incoming request to dedicated handler.
     *
     * @param <CTX>
     *            Context type in which request are processed
     * @param <REQ>
     *            Type of routed request
     * @param <REP>
     *            Type of routed response
     * @param routes
     *            {@link Route} where request will be forwarded to
     * @param defaultRoute
     *            If no route can be applied for a specific request, it'll be forwarded to the defaultRoute
     * @return A new {@link ReactiveHandler} routing incoming requests
     */
    public static <CTX, REQ, REP> ReactiveHandler<CTX, REQ, REP> routeTo(final Iterable<Route<CTX, REQ, REP>> routes,
            final ReactiveHandler<CTX, REQ, REP> defaultRoute) {
        /** Routes requests. */
        final class RouterHandler implements ReactiveHandler<CTX, REQ, REP> {
            @Override
            public Single<REP> handle(final CTX context, final REQ request) throws Exception {
                for (final Route<CTX, REQ, REP> route : routes) {
                    if (route.predicate.matches(request, context)) {
                        return route.handler.handle(context, request);
                    }
                }
                return defaultRoute.handle(context, request);
            }
        }
        return new RouterHandler();
    }
    /**
     * Allows to transform all aspects of a {@link Request}.
     *
     * @param <CTX>
     *            Context type in which request are processed
     * @param requestTransformer
     *            Function in charge of performing the {@link Request} transformation
     * @param responseTransformer
     *            Function in charge of performing the {@link Response} transformation
     * @return A new policy performing the {@link Request} and {@link Response} transformation.
     */
    public static <CTX> SimpleReactiveFilter<CTX, Request, Stream<Response>> transform(
            final Function<Request, Request, Exception> requestTransformer,
            final Function<Response, Response, Exception> responseTransformer) {
        /** Transforms {@link Request} and {@link Response}. */
        final class TransformFilter extends SimpleReactiveFilter<CTX, Request, Stream<Response>> {
            @Override
            public Single<Stream<Response>> filter(final CTX context, final Request request,
                    final ReactiveHandler<CTX, Request, Stream<Response>> next) throws Exception {
                return next.handle(context, requestTransformer.apply(request))
                        .map(new Function<Stream<Response>, Stream<Response>, Exception>() {
                            @Override
                            public Stream<Response> apply(Stream<Response> responses) throws Exception {
                                return responses.map(new Function<Response, Response, Exception>() {
                                    @Override
                                    public Response apply(Response value) throws Exception {
                                        return responseTransformer.apply(value);
                                    }
                                });
                            }
                        });
            }
        }
        return new TransformFilter();
    }
    /**
     * Forward {@link Request} to the provided {@link LDAPConnectionFactory}.
     *
     * @param connectionFactory
     *            The {@link LDAPConnectionFactory} used to send the forwarded {@link Request}
     * @return a {@link ReactiveHandler} Forwarding {@link Request} to the {@link LDAPConnectionFactory}
     */
    public static ReactiveHandler<LDAPClientConnection2, Request, Stream<Response>> proxyTo(
            LDAPConnectionFactory connectionFactory) {
        return new ProxyToHandler(connectionFactory);
    }
    /**
     * {@link ReactiveHandler} responding with the provided Response for all incoming requests.
     *
     * @param <CTX>
     *            Context type in which request are processed
     * @param response
     *            The {@link Response} to send as response for all {@link Request}
     * @return a {@link ReactiveHandler} replying {@link Response} for all {@link Request}
     */
    public static <CTX> ReactiveHandler<CTX, Request, Stream<Response>> respondWith(final Response response) {
        return new ReactiveHandler<CTX, Request, Stream<Response>>() {
            @Override
            public Single<Stream<Response>> handle(final CTX context, final Request request) {
                if (request instanceof UnbindRequest) {
                    return singleFrom(RxJavaStreams.<Response>emptyStream());
                }
                return singleFrom(streamFrom(response));
            }
        };
    }
    /**
     * Decodes incoming {@link LdapRawMessage} into a {@link Request}.
     *
     * @param decodeOptions
     *            {@link DecodeOptions} used during {@link Request} decoding
     * @return a {@link ReactiveFilter} decoding {@link LdapRawMessage} into {@link Request}
     */
    public static ReactiveFilter<LDAPClientConnection2, LdapRawMessage, Stream<Response>,
                                                        Request, Stream<Response>>
        decodeRequest(final DecodeOptions decodeOptions) {
        return new ReactiveFilter<LDAPClientConnection2, LdapRawMessage, Stream<Response>,
                                                         Request, Stream<Response>>() {
            @Override
            public Single<Stream<Response>> filter(final LDAPClientConnection2 context,
                    final LdapRawMessage encodedRequestMessage,
                    final ReactiveHandler<LDAPClientConnection2, Request, Stream<Response>> next) throws Exception {
                return newSingle(new Single.OnSubscribe<Request>() {
                    @Override
                    public void onSubscribe(final Single.Emitter<Request> emitter) throws Exception {
                        LDAP.getReader(encodedRequestMessage.getContent(), decodeOptions)
                                .readMessage(new AbstractLDAPMessageHandler() {
                            @Override
                            public void abandonRequest(final int messageID, final AbandonRequest request)
                                    throws DecodeException, IOException {
                                emitter.onSuccess(request);
                            }
                            @Override
                            public void addRequest(int messageID, AddRequest request)
                                    throws DecodeException, IOException {
                                emitter.onSuccess(request);
                            }
                            @Override
                            public void bindRequest(int messageID, int version, GenericBindRequest request)
                                    throws DecodeException, IOException {
                                emitter.onSuccess(request);
                            }
                            @Override
                            public void modifyDNRequest(int messageID, ModifyDNRequest request)
                                    throws DecodeException, IOException {
                                emitter.onSuccess(request);
                            }
                            @Override
                            public void modifyRequest(int messageID, ModifyRequest request)
                                    throws DecodeException, IOException {
                                emitter.onSuccess(request);
                            }
                            @Override
                            public void searchRequest(int messageID, SearchRequest request)
                                    throws DecodeException, IOException {
                                emitter.onSuccess(request);
                            }
                            @Override
                            public void unbindRequest(int messageID, UnbindRequest request)
                                    throws DecodeException, IOException {
                                emitter.onSuccess(request);
                            }
                        });
                    }
                }).flatMap(new Function<Request, Publisher<Stream<Response>>, Exception>() {
                    @Override
                    public Publisher<Stream<Response>> apply(Request t) throws Exception {
                        return next.handle(context, t);
                    }
                });
            }
        };
    }
    /**
     * Invoke the following {@link ReactiveFilter} from the given {@link Executor}.
     *
     * @param <CTX>
     *            Context type in which request are processed
     * @param <REQ>
     *            Type of dispatched request
     * @param <REP>
     *            Type of dispatched response
     * @param executor
     *            The {@link Executor} used to forward the request
     * @return A {@link ReactiveFilter} fowarding {@link Request} through the {@link Executor}
     */
    public static <CTX, REQ, REP> ReactiveFilter<CTX, REQ, REP, REQ, REP> dispatch(final Executor executor) {
        /** Dispatches request into an {@link Executor}. */
        final class DispatchFilter extends SimpleReactiveFilter<CTX, REQ, REP> {
            private final Scheduler executor;
            DispatchFilter(final Executor executor) {
                this.executor = Schedulers.from(checkNotNull(executor, "executor must not be null"));
            }
            @Override
            public Single<REP> filter(final CTX context, final REQ request,
                    final ReactiveHandler<CTX, REQ, REP> next) {
                return singleFromPublisher(Flowable.defer(new Callable<Publisher<REP>>() {
                    @Override
                    public Publisher<REP> call() throws Exception {
                        return next.handle(context, request);
                    }
                }).subscribeOn(executor));
            }
        }
        return new DispatchFilter(executor);
    }
    private Components() {
        // Prevent instantiation
    }
}
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
New file
@@ -0,0 +1,1944 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2010-2016 ForgeRock AS.
 */
package org.forgerock.opendj.reactive;
import static com.forgerock.reactive.RxJavaStreams.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.loggers.AccessLogger.logDisconnect;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.ServerConstants.OID_START_TLS_REQUEST;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import java.net.InetAddress;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.cert.Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.adapter.server3x.Converters;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConnectionHandler;
import org.opends.server.core.AbandonOperationBasis;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.BindOperationBasis;
import org.opends.server.core.CompareOperationBasis;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ExtendedOperationBasis;
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.QueueingStrategy;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.SearchOperationBasis;
import org.opends.server.core.UnbindOperationBasis;
import org.opends.server.extensions.TLSCapableConnection;
import org.opends.server.protocols.ldap.AbandonRequestProtocolOp;
import org.opends.server.protocols.ldap.AddRequestProtocolOp;
import org.opends.server.protocols.ldap.BindRequestProtocolOp;
import org.opends.server.protocols.ldap.CompareRequestProtocolOp;
import org.opends.server.protocols.ldap.DeleteRequestProtocolOp;
import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPReader;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.protocols.ldap.LDAPStatistics;
import org.opends.server.protocols.ldap.ModifyDNRequestProtocolOp;
import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
import org.opends.server.types.AuthenticationType;
import org.opends.server.types.CancelRequest;
import org.opends.server.types.CancelResult;
import org.opends.server.types.Control;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.IntermediateResponse;
import org.opends.server.types.Operation;
import org.opends.server.types.OperationType;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.util.TimeThread;
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.Single;
import com.forgerock.reactive.Stream;
import io.reactivex.BackpressureOverflowStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableEmitter.BackpressureMode;
import io.reactivex.FlowableOnSubscribe;
/**
 * This class defines an LDAP client connection, which is a type of
 * client connection that will be accepted by an instance of the LDAP
 * connection handler and have its requests decoded by an LDAP request
 * handler.
 */
public final class LDAPClientConnection2 extends ClientConnection implements
    TLSCapableConnection, ReactiveHandler<QueueingStrategy, LdapRawMessage, Stream<Response>>
{
  private static final String REACTIVE_OUT = "reactive.out";
/** The tracer object for the debug logger. */
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** The time that the last operation was completed. */
  private final AtomicLong lastCompletionTime;
  /** The next operation ID that should be used for this connection. */
  private final AtomicLong nextOperationID;
  /**
   * Indicates whether the Directory Server believes this connection to be valid
   * and available for communication.
   */
  private volatile boolean connectionValid;
  /**
   * Indicates whether this connection is about to be closed. This will be used
   * to prevent accepting new requests while a disconnect is in progress.
   */
  private boolean disconnectRequested;
  /**
   * Indicates whether the connection should keep statistics regarding the
   * operations that it is performing.
   */
  private final boolean keepStats;
  /** The set of all operations currently in progress on this connection. */
  private final ConcurrentHashMap<Integer, Operation> operationsInProgress;
  /**
   * The number of operations performed on this connection. Used to compare with
   * the resource limits of the network group.
   */
  private final AtomicLong operationsPerformed;
  /** The port on the client from which this connection originated. */
  private final int clientPort;
  /** The LDAP version that the client is using to communicate with the server. */
  private int ldapVersion;
  /** The port on the server to which this client has connected. */
  private final int serverPort;
  /** The reference to the connection handler that accepted this connection. */
  private final LDAPConnectionHandler2 connectionHandler;
  /** The statistics tracker associated with this client connection. */
  private final LDAPStatistics statTracker;
  private final boolean useNanoTime;
  /** The connection ID assigned to this connection. */
  private final long connectionID;
  /** The lock used to provide threadsafe access to the set of operations in progress. */
  private final Object opsInProgressLock;
  /** The socket channel with which this client connection is associated. */
  private final LDAPClientContext clientContext;
  /** The string representation of the address of the client. */
  private final String clientAddress;
  /** The name of the protocol that the client is using to communicate with the server. */
  private final String protocol;
  /** The string representation of the address of the server to which the client has connected. */
  private final String serverAddress;
  /**
   * Creates a new LDAP client connection with the provided information.
   *
   * @param connectionHandler
   *          The connection handler that accepted this connection.
   * @param clientContext
   *          The socket channel that may be used to communicate with
   *          the client.
   * @param  protocol String representing the protocol (LDAP or LDAP+SSL).
   * @throws LdapException
   * @throws DirectoryException If SSL initialisation fails.
   */
  LDAPClientConnection2(LDAPConnectionHandler2 connectionHandler, LDAPClientContext clientContext, String protocol,
          boolean keepStats)
  {
    this.connectionHandler = connectionHandler;
    this.clientContext = clientContext;
    opsInProgressLock = new Object();
    ldapVersion = 3;
    lastCompletionTime = new AtomicLong(TimeThread.getTime());
    nextOperationID = new AtomicLong(0);
    connectionValid = true;
    disconnectRequested = false;
    operationsInProgress = new ConcurrentHashMap<>();
    operationsPerformed = new AtomicLong(0);
    this.keepStats = keepStats;
    this.protocol = protocol;
    clientAddress = clientContext.getPeerAddress().getAddress().getHostAddress();
    clientPort = clientContext.getPeerAddress().getPort();
    serverAddress = clientContext.getLocalAddress().getAddress().getHostAddress();
    serverPort = clientContext.getLocalAddress().getPort();
    statTracker = this.connectionHandler.getStatTracker();
    if (keepStats)
    {
      statTracker.updateConnect();
      this.useNanoTime = DirectoryServer.getUseNanoTime();
    }
    else
    {
      this.useNanoTime = false;
    }
    connectionID = DirectoryServer.newConnectionAccepted(this);
  }
  /**
   * Retrieves the connection ID assigned to this connection.
   *
   * @return The connection ID assigned to this connection.
   */
  @Override
  public long getConnectionID()
  {
    return connectionID;
  }
  /**
   * Retrieves the connection handler that accepted this client
   * connection.
   *
   * @return The connection handler that accepted this client
   *         connection.
   */
  @Override
  public ConnectionHandler<?> getConnectionHandler()
  {
    return connectionHandler;
  }
  /**
   * Retrieves the socket channel that can be used to communicate with
   * the client.
   *
   * @return The socket channel that can be used to communicate with the
   *         client.
   */
  @Override
  public SocketChannel getSocketChannel()
  {
    throw new UnsupportedOperationException();
  }
  /**
   * Retrieves the protocol that the client is using to communicate with
   * the Directory Server.
   *
   * @return The protocol that the client is using to communicate with
   *         the Directory Server.
   */
  @Override
  public String getProtocol()
  {
    return protocol;
  }
  /**
   * Retrieves a string representation of the address of the client.
   *
   * @return A string representation of the address of the client.
   */
  @Override
  public String getClientAddress()
  {
    return clientAddress;
  }
  /**
   * Retrieves the port number for this connection on the client system.
   *
   * @return The port number for this connection on the client system.
   */
  @Override
  public int getClientPort()
  {
    return clientPort;
  }
  /**
   * Retrieves a string representation of the address on the server to
   * which the client connected.
   *
   * @return A string representation of the address on the server to
   *         which the client connected.
   */
  @Override
  public String getServerAddress()
  {
    return serverAddress;
  }
  /**
   * Retrieves the port number for this connection on the server system.
   *
   * @return The port number for this connection on the server system.
   */
  @Override
  public int getServerPort()
  {
    return serverPort;
  }
  /**
   * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the
   * remote client system.
   *
   * @return The <CODE>java.net.InetAddress</CODE> associated with the
   *         remote client system. It may be <CODE>null</CODE> if the
   *         client is not connected over an IP-based connection.
   */
  @Override
  public InetAddress getRemoteAddress()
  {
    return clientContext.getPeerAddress().getAddress();
  }
  /**
   * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory
   * Server system to which the client has established the connection.
   *
   * @return The <CODE>java.net.InetAddress</CODE> for the Directory
   *         Server system to which the client has established the
   *         connection. It may be <CODE>null</CODE> if the client is
   *         not connected over an IP-based connection.
   */
  @Override
  public InetAddress getLocalAddress()
  {
    return clientContext.getLocalAddress().getAddress();
  }
  @Override
  public boolean isConnectionValid()
  {
    return this.connectionValid;
  }
  /**
   * Indicates whether this client connection is currently using a
   * secure mechanism to communicate with the server. Note that this may
   * change over time based on operations performed by the client or
   * server (e.g., it may go from <CODE>false</CODE> to
   * <CODE>true</CODE> if the client uses the StartTLS extended
   * operation).
   *
   * @return <CODE>true</CODE> if the client connection is currently
   *         using a secure mechanism to communicate with the server, or
   *         <CODE>false</CODE> if not.
   */
  @Override
  public boolean isSecure()
  {
    return false;
  }
  /**
   * Sends a response to the client based on the information in the
   * provided operation.
   *
   * @param operation
   *          The operation for which to send the response.
   */
  @Override
  public void sendResponse(Operation operation)
  {
    // Since this is the final response for this operation, we can go
    // ahead and remove it from the "operations in progress" list. It
    // can't be canceled after this point, and this will avoid potential
    // race conditions in which the client immediately sends another
    // request with the same message ID as was used for this operation.
    if (keepStats) {
        long time;
        if (useNanoTime) {
            time = operation.getProcessingNanoTime();
        } else {
            time = operation.getProcessingTime();
        }
        this.statTracker.updateOperationMonitoringData(
                operation.getOperationType(),
                time);
    }
    // Avoid sending the response if one has already been sent. This may happen
    // if operation processing encounters a run-time exception after sending the
    // response: the worker thread exception handling code will attempt to send
    // an error result to the client indicating that a problem occurred.
    if (removeOperationInProgress(operation.getMessageID()))
    {
      final Response response = operationToResponse(operation);
      final FlowableEmitter<Response> out = getOut(operation);
      if (response != null)
      {
        out.onNext(response);
      }
      out.onComplete();
    }
  }
  /**
   * Retrieves an LDAPMessage containing a response generated from the
   * provided operation.
   *
   * @param operation
   *          The operation to use to generate the response LDAPMessage.
   * @return An LDAPMessage containing a response generated from the
   *         provided operation.
   */
  private Response operationToResponse(Operation operation)
  {
    ResultCode resultCode = operation.getResultCode();
    if (resultCode == null)
    {
      // This must mean that the operation has either not yet completed
      // or that it completed without a result for some reason. In any
      // case, log a message and set the response to "operations error".
      logger.error(ERR_LDAP_CLIENT_SEND_RESPONSE_NO_RESULT_CODE, operation.getOperationType(),
          operation.getConnectionID(), operation.getOperationID());
      resultCode = DirectoryServer.getServerErrorResultCode();
    }
    LocalizableMessageBuilder errorMessage = operation.getErrorMessage();
    String matchedDN = operation.getMatchedDN() != null ? operation.getMatchedDN().toString() : null;
    // Referrals are not allowed for LDAPv2 clients.
    List<String> referralURLs;
    if (ldapVersion == 2)
    {
      referralURLs = null;
      if (resultCode == ResultCode.REFERRAL)
      {
        resultCode = ResultCode.CONSTRAINT_VIOLATION;
        errorMessage.append(ERR_LDAPV2_REFERRAL_RESULT_CHANGED.get());
      }
      List<String> opReferrals = operation.getReferralURLs();
      if (opReferrals != null && !opReferrals.isEmpty())
      {
        StringBuilder referralsStr = new StringBuilder();
        Iterator<String> iterator = opReferrals.iterator();
        referralsStr.append(iterator.next());
        while (iterator.hasNext())
        {
          referralsStr.append(", ");
          referralsStr.append(iterator.next());
        }
        errorMessage.append(ERR_LDAPV2_REFERRALS_OMITTED.get(referralsStr));
      }
    }
    else
    {
      referralURLs = operation.getReferralURLs();
    }
    final Result result;
    switch (operation.getOperationType())
    {
    case ADD:
      result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN);
      break;
    case BIND:
      result = Responses.newBindResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN)
                        .setServerSASLCredentials(((BindOperationBasis) operation).getServerSASLCredentials());
      break;
    case COMPARE:
      result = Responses.newCompareResult(resultCode)
                        .setDiagnosticMessage(errorMessage.toString())
                        .setMatchedDN(matchedDN);
      break;
    case DELETE:
      result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN);
      break;
    case EXTENDED:
      // If this an LDAPv2 client, then we can't send this.
      if (ldapVersion == 2)
      {
        logger.error(ERR_LDAPV2_SKIPPING_EXTENDED_RESPONSE,
            getConnectionID(), operation.getOperationID(), operation);
        return null;
      }
      ExtendedOperationBasis extOp = (ExtendedOperationBasis) operation;
      result = Responses.newGenericExtendedResult(resultCode)
                        .setDiagnosticMessage(errorMessage.toString())
                        .setMatchedDN(matchedDN)
                        .setOID(extOp.getResponseOID()).setValue(extOp.getResponseValue());
      break;
    case MODIFY:
      result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN);
      break;
    case MODIFY_DN:
      result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN);
      break;
    case SEARCH:
      result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN);
      break;
    default:
      // This must be a type of operation that doesn't have a response.
      // This shouldn't happen, so log a message and return.
      logger.error(ERR_LDAP_CLIENT_SEND_RESPONSE_INVALID_OP, operation.getOperationType(), getConnectionID(),
          operation.getOperationID(), operation);
      return null;
    }
    if (referralURLs != null)
    {
      result.getReferralURIs().addAll(referralURLs);
    }
    // Controls are not allowed for LDAPv2 clients.
    if (ldapVersion != 2)
    {
      for(Control control : operation.getResponseControls()) {
        result.addControl(Converters.from(control));
      }
    }
    return result;
  }
  /**
   * Sends the provided search result entry to the client.
   *
   * @param searchOperation
   *          The search operation with which the entry is associated
   * @param searchEntry
   *          The search result entry to be sent to the client
   */
  @Override
  public void sendSearchEntry(SearchOperation searchOperation, SearchResultEntry searchEntry)
  {
    getEmitter(searchOperation).onNext(toResponse(searchEntry));
  }
  private FlowableEmitter<Response> getEmitter(SearchOperation searchOperation)
  {
    return getOut(searchOperation);
  }
  private Response toResponse(SearchResultEntry searchEntry)
  {
    return Responses.newSearchResultEntry(Converters.partiallyWrap(searchEntry));
  }
  private FlowableEmitter<Response> getOut(Operation operation)
  {
    return ((FlowableEmitter<Response>) operation.getAttachment(REACTIVE_OUT));
  }
  /**
   * Sends the provided search result reference to the client.
   *
   * @param searchOperation
   *          The search operation with which the reference is
   *          associated.
   * @param searchReference
   *          The search result reference to be sent to the client.
   * @return <CODE>true</CODE> if the client is able to accept
   *         referrals, or <CODE>false</CODE> if the client cannot
   *         handle referrals and no more attempts should be made to
   *         send them for the associated search operation.
   */
  @Override
  public boolean sendSearchReference(SearchOperation searchOperation, SearchResultReference searchReference)
  {
    // Make sure this is not an LDAPv2 client. If it is, then they can't
    // see referrals so we'll not send anything. Also, throw an
    // exception so that the core server will know not to try sending
    // any more referrals to this client for the rest of the operation.
    if (ldapVersion == 2)
    {
      logger.error(ERR_LDAPV2_SKIPPING_SEARCH_REFERENCE, getConnectionID(),
              searchOperation.getOperationID(), searchReference);
      return false;
    }
    final FlowableEmitter<Response> out = getOut(searchOperation);
    out.onNext(Converters.from(searchReference));
    return true;
  }
  /**
   * Sends the provided intermediate response message to the client.
   *
   * @param intermediateResponse
   *          The intermediate response message to be sent.
   * @return <CODE>true</CODE> if processing on the associated operation
   *         should continue, or <CODE>false</CODE> if not.
   */
  @Override
  protected boolean sendIntermediateResponseMessage(IntermediateResponse intermediateResponse)
  {
    final Operation operation = intermediateResponse.getOperation();
    final FlowableEmitter<Response> out = getOut(operation);
    final Response response =
        Responses.newGenericIntermediateResponse(intermediateResponse.getOID(), intermediateResponse.getValue());
    for (Control control : intermediateResponse.getControls())
    {
      response.addControl(Converters.from(control));
    }
    out.onNext(response);
    // The only reason we shouldn't continue processing is if the
    // connection is closed.
    return connectionValid;
  }
  /**
   * Closes the connection to the client, optionally sending it a
   * message indicating the reason for the closure. Note that the
   * ability to send a notice of disconnection may not be available for
   * all protocols or under all circumstances.
   *
   * @param disconnectReason
   *          The disconnect reason that provides the generic cause for
   *          the disconnect.
   * @param sendNotification
   *          Indicates whether to try to provide notification to the
   *          client that the connection will be closed.
   * @param message
   *          The message to include in the disconnect notification
   *          response. It may be <CODE>null</CODE> if no message is to
   *          be sent.
   */
  @Override
  public void disconnect(DisconnectReason disconnectReason,
      boolean sendNotification, LocalizableMessage message)
  {
    // Set a flag indicating that the connection is being terminated so
    // that no new requests will be accepted. Also cancel all operations
    // in progress.
    synchronized (opsInProgressLock)
    {
      // If we are already in the middle of a disconnect, then don't
      // do anything.
      if (disconnectRequested)
      {
        return;
      }
      disconnectRequested = true;
    }
    if (keepStats)
    {
      statTracker.updateDisconnect();
    }
    if (connectionID >= 0)
    {
      DirectoryServer.connectionClosed(this);
    }
    // Indicate that this connection is no longer valid.
    connectionValid = false;
    final LocalizableMessage cancelMessage;
    if (message != null)
    {
      cancelMessage = new LocalizableMessageBuilder()
          .append(disconnectReason.getClosureMessage())
          .append(": ")
          .append(message)
          .toMessage();
    }
    else
    {
      cancelMessage = disconnectReason.getClosureMessage();
    }
    cancelAllOperations(new CancelRequest(true, cancelMessage));
    finalizeConnectionInternal();
    // See if we should send a notification to the client. If so, then
    // construct and send a notice of disconnection unsolicited
    // response. Note that we cannot send this notification to an LDAPv2 client.
    if (sendNotification && ldapVersion != 2)
    {
      try
      {
        LocalizableMessage errMsg = message != null ? message : INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get();
        clientContext.disconnect(ResultCode.valueOf(toResultCode(disconnectReason)), errMsg.toString());
      }
      catch (Exception e)
      {
        // NYI -- Log a message indicating that we couldn't send the
        // notice of disconnection.
        logger.traceException(e);
      }
    }
    // NYI -- Deregister the client connection from any server components that
    // might know about it.
    logDisconnect(this, disconnectReason, message);
    try
    {
      PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager();
      pluginManager.invokePostDisconnectPlugins(this, disconnectReason, message);
    }
    catch (Exception e)
    {
      logger.traceException(e);
    }
  }
  private int toResultCode(DisconnectReason disconnectReason)
  {
    switch (disconnectReason)
    {
    case PROTOCOL_ERROR:
      return LDAPResultCode.PROTOCOL_ERROR;
    case SERVER_SHUTDOWN:
      return LDAPResultCode.UNAVAILABLE;
    case SERVER_ERROR:
      return DirectoryServer.getServerErrorResultCode().intValue();
    case ADMIN_LIMIT_EXCEEDED:
    case IDLE_TIME_LIMIT_EXCEEDED:
    case MAX_REQUEST_SIZE_EXCEEDED:
    case IO_TIMEOUT:
      return LDAPResultCode.ADMIN_LIMIT_EXCEEDED;
    case CONNECTION_REJECTED:
      return LDAPResultCode.CONSTRAINT_VIOLATION;
    case INVALID_CREDENTIALS:
      return LDAPResultCode.INVALID_CREDENTIALS;
    default:
      return LDAPResultCode.OTHER;
    }
  }
  /**
   * Retrieves the set of operations in progress for this client
   * connection. This list must not be altered by any caller.
   *
   * @return The set of operations in progress for this client
   *         connection.
   */
  @Override
  public Collection<Operation> getOperationsInProgress()
  {
    return operationsInProgress.values();
  }
  /**
   * Retrieves the operation in progress with the specified message ID.
   *
   * @param messageID
   *          The message ID for the operation to retrieve.
   * @return The operation in progress with the specified message ID, or
   *         <CODE>null</CODE> if no such operation could be found.
   */
  @Override
  public Operation getOperationInProgress(int messageID)
  {
    return operationsInProgress.get(messageID);
  }
  /**
   * Adds the provided operation to the set of operations in progress
   * for this client connection.
   *
   * @param operation
   *          The operation to add to the set of operations in progress
   *          for this client connection.
   * @throws DirectoryException
   *           If the operation is not added for some reason (e.g., the
   *           client already has reached the maximum allowed concurrent
   *           requests).
   */
  private void addOperationInProgress(final QueueingStrategy queueingStrategy, Operation operation)
      throws DirectoryException
  {
    int messageID = operation.getMessageID();
    // We need to grab a lock to ensure that no one else can add
    // operations to the queue while we are performing some preliminary
    // checks.
    try
    {
      synchronized (opsInProgressLock)
      {
        // If we're already in the process of disconnecting the client,
        // then reject the operation.
        if (disconnectRequested)
        {
          LocalizableMessage message = WARN_CLIENT_DISCONNECT_IN_PROGRESS.get();
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
              message);
        }
        // Add the operation to the list of operations in progress for
        // this connection.
        Operation op = operationsInProgress.putIfAbsent(messageID, operation);
        // See if there is already an operation in progress with the
        // same message ID. If so, then we can't allow it.
        if (op != null)
        {
          LocalizableMessage message =
            WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID);
          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
              message);
        }
      }
      // Try to add the operation to the work queue,
      // or run it synchronously (typically for the administration
      // connector)
      queueingStrategy.enqueueRequest(operation);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      operationsInProgress.remove(messageID);
      lastCompletionTime.set(TimeThread.getTime());
      throw de;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      LocalizableMessage message =
        WARN_LDAP_CLIENT_CANNOT_ENQUEUE.get(getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer
          .getServerErrorResultCode(), message, e);
    }
  }
  /**
   * Removes the provided operation from the set of operations in
   * progress for this client connection. Note that this does not make
   * any attempt to cancel any processing that may already be in
   * progress for the operation.
   *
   * @param messageID
   *          The message ID of the operation to remove from the set of
   *          operations in progress.
   * @return <CODE>true</CODE> if the operation was found and removed
   *         from the set of operations in progress, or
   *         <CODE>false</CODE> if not.
   */
  @Override
  public boolean removeOperationInProgress(int messageID)
  {
    Operation operation = operationsInProgress.remove(messageID);
    if (operation == null)
    {
      return false;
    }
    if (operation.getOperationType() == OperationType.ABANDON
        && keepStats
        && operation.getResultCode() == ResultCode.CANCELLED)
    {
      statTracker.updateAbandonedOperation();
    }
    lastCompletionTime.set(TimeThread.getTime());
    return true;
  }
  /**
   * Attempts to cancel the specified operation.
   *
   * @param messageID
   *          The message ID of the operation to cancel.
   * @param cancelRequest
   *          An object providing additional information about how the
   *          cancel should be processed.
   * @return A cancel result that either indicates that the cancel was
   *         successful or provides a reason that it was not.
   */
  @Override
  public CancelResult cancelOperation(int messageID,
      CancelRequest cancelRequest)
  {
    Operation op = operationsInProgress.get(messageID);
    if (op != null)
    {
      return op.cancel(cancelRequest);
    }
    // See if the operation is in the list of persistent searches.
    for (PersistentSearch ps : getPersistentSearches())
    {
      if (ps.getMessageID() == messageID)
      {
        // We only need to find the first persistent search
        // associated with the provided message ID. The persistent search
        // will ensure that all other related persistent searches are cancelled.
        return ps.cancel();
      }
    }
    return new CancelResult(ResultCode.NO_SUCH_OPERATION, null);
  }
  /**
   * Attempts to cancel all operations in progress on this connection.
   *
   * @param cancelRequest
   *          An object providing additional information about how the
   *          cancel should be processed.
   */
  @Override
  public void cancelAllOperations(CancelRequest cancelRequest)
  {
    // Make sure that no one can add any new operations.
    synchronized (opsInProgressLock)
    {
      try
      {
        for (Operation o : operationsInProgress.values())
        {
          try
          {
            o.abort(cancelRequest);
            // TODO: Assume its cancelled?
            if (keepStats)
            {
              statTracker.updateAbandonedOperation();
            }
          }
          catch (Exception e)
          {
            logger.traceException(e);
          }
        }
        if (!operationsInProgress.isEmpty()
            || !getPersistentSearches().isEmpty())
        {
          lastCompletionTime.set(TimeThread.getTime());
        }
        operationsInProgress.clear();
        for (PersistentSearch persistentSearch : getPersistentSearches())
        {
          persistentSearch.cancel();
        }
      }
      catch (Exception e)
      {
        logger.traceException(e);
      }
    }
  }
  /**
   * Attempts to cancel all operations in progress on this connection
   * except the operation with the specified message ID.
   *
   * @param cancelRequest
   *          An object providing additional information about how the
   *          cancel should be processed.
   * @param messageID
   *          The message ID of the operation that should not be
   *          canceled.
   */
  @Override
  public void cancelAllOperationsExcept(CancelRequest cancelRequest,
      int messageID)
  {
    // Make sure that no one can add any new operations.
    synchronized (opsInProgressLock)
    {
      try
      {
        for (int msgID : operationsInProgress.keySet())
        {
          if (msgID == messageID)
          {
            continue;
          }
          Operation o = operationsInProgress.get(msgID);
          if (o != null)
          {
            try
            {
              o.abort(cancelRequest);
              // TODO: Assume its cancelled?
              if (keepStats)
              {
                statTracker.updateAbandonedOperation();
              }
            }
            catch (Exception e)
            {
              logger.traceException(e);
            }
          }
          operationsInProgress.remove(msgID);
          lastCompletionTime.set(TimeThread.getTime());
        }
        for (PersistentSearch persistentSearch : getPersistentSearches())
        {
          if (persistentSearch.getMessageID() == messageID)
          {
            continue;
          }
          persistentSearch.cancel();
          lastCompletionTime.set(TimeThread.getTime());
        }
      }
      catch (Exception e)
      {
        logger.traceException(e);
      }
    }
  }
  @Override
  public Selector getWriteSelector()
  {
    throw new UnsupportedOperationException();
  }
  @Override
  public long getMaxBlockedWriteTimeLimit()
  {
    return connectionHandler.getMaxBlockedWriteTimeLimit();
  }
  /**
   * Returns the total number of operations initiated on this
   * connection.
   *
   * @return the total number of operations on this connection
   */
  @Override
  public long getNumberOfOperations()
  {
    return operationsPerformed.get();
  }
  /**
   * Processes the provided LDAP message read from the client and takes
   * whatever action is appropriate. For most requests, this will
   * include placing the operation in the work queue. Certain requests
   * (in particular, abandons and unbinds) will be processed directly.
   *
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message to process.
   * @return <CODE>true</CODE> if the appropriate action was taken for
   *         the request, or <CODE>false</CODE> if there was a fatal
   *         error and the client has been disconnected as a result, or
   *         if the client unbound from the server.
   */
    @Override
    public Single<Stream<Response>> handle(final QueueingStrategy queueingStrategy, final LdapRawMessage message) {
        return singleFrom(streamFromPublisher(Flowable.create(new FlowableOnSubscribe<Response>() {
            @Override
            public void subscribe(FlowableEmitter<Response> emitter) throws Exception {
                processLDAPMessage(queueingStrategy, LDAPReader.readMessage(message.getContent()), emitter);
            }
        }, BackpressureMode.NONE).onBackpressureBuffer(64, null, BackpressureOverflowStrategy.ERROR)));
    }
  private boolean processLDAPMessage(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final FlowableEmitter<Response> out)
  {
    if (keepStats)
    {
      statTracker.updateMessageRead(message);
    }
    operationsPerformed.getAndIncrement();
    List<Control> opControls = message.getControls();
    // FIXME -- See if there is a bind in progress. If so, then deny
    // most kinds of operations.
    // Figure out what type of operation we're dealing with based on the
    // LDAP message. Abandon and unbind requests will be processed here.
    // All other types of requests will be encapsulated into operations
    // and append into the work queue to be picked up by a worker
    // thread. Any other kinds of LDAP messages (e.g., response
    // messages) are illegal and will result in the connection being
    // terminated.
    try
    {
      if (bindInProgress.get())
      {
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_BIND_IN_PROGRESS.get());
      }
      else if (startTLSInProgress.get())
      {
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_STARTTLS_IN_PROGRESS.get());
      }
      else if (saslBindInProgress.get() && message.getProtocolOpType() != OP_TYPE_BIND_REQUEST)
      {
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_SASLBIND_IN_PROGRESS.get());
      }
      boolean result;
      switch (message.getProtocolOpType())
      {
      case OP_TYPE_ABANDON_REQUEST:
        return processAbandonRequest(queueingStrategy, message, opControls, out);
      case OP_TYPE_ADD_REQUEST:
        return processAddRequest(queueingStrategy, message, opControls, out);
      case OP_TYPE_BIND_REQUEST:
        boolean isSaslBind = message.getBindRequestProtocolOp().getAuthenticationType() == AuthenticationType.SASL;
        bindInProgress.set(true);
        if (isSaslBind)
        {
          saslBindInProgress.set(true);
        }
        result = processBindRequest(queueingStrategy, message, opControls, out);
        if(!result)
        {
          bindInProgress.set(false);
          if (isSaslBind)
          {
            saslBindInProgress.set(false);
          }
        }
        return result;
      case OP_TYPE_COMPARE_REQUEST:
        return processCompareRequest(queueingStrategy, message, opControls, out);
      case OP_TYPE_DELETE_REQUEST:
        return processDeleteRequest(queueingStrategy, message, opControls, out);
      case OP_TYPE_EXTENDED_REQUEST:
        boolean isStartTlsRequest = OID_START_TLS_REQUEST.equals(message.getExtendedRequestProtocolOp().getOID());
        if (isStartTlsRequest)
        {
          startTLSInProgress.set(true);
        }
        result = processExtendedRequest(queueingStrategy, message, opControls, out);
        if (!result && isStartTlsRequest)
        {
          startTLSInProgress.set(false);
        }
        return result;
      case OP_TYPE_MODIFY_REQUEST:
        return processModifyRequest(queueingStrategy, message, opControls, out);
      case OP_TYPE_MODIFY_DN_REQUEST:
        return processModifyDNRequest(queueingStrategy, message, opControls, out);
      case OP_TYPE_SEARCH_REQUEST:
        return processSearchRequest(queueingStrategy, message, opControls, out);
      case OP_TYPE_UNBIND_REQUEST:
        return processUnbindRequest(message, opControls);
      default:
        LocalizableMessage msg =
            ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(message
                .getProtocolOpName(), message.getMessageID());
        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
        return false;
      }
    }
    catch (Exception e)
    {
      logger.traceException(e);
      LocalizableMessage msg =
          ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(message
              .getProtocolOpName(), message.getMessageID(), e);
      disconnect(DisconnectReason.SERVER_ERROR, true, msg);
      return false;
    }
  }
  /**
   * Processes the provided LDAP message as an abandon request.
   *
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the abandon request to
   *          process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processAbandonRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final List<Control> controls, final FlowableEmitter<Response> out)
  {
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      disconnectControlsNotAllowed();
      return false;
    }
    // Create the abandon operation and add it into the work queue.
    AbandonRequestProtocolOp protocolOp =
        message.getAbandonRequestProtocolOp();
    AbandonOperationBasis abandonOp =
        new AbandonOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getIDToAbandon());
    abandonOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, abandonOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      // Don't send an error response since abandon operations
      // don't have a response.
    }
    return connectionValid;
  }
  /**
   * Processes the provided LDAP message as an add request.
   *
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the add request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processAddRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final List<Control> controls, final FlowableEmitter<Response> out)
  {
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR)
                          .setDiagnosticMessage(ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get().toString()));
      out.onComplete();
      disconnectControlsNotAllowed();
      return false;
    }
    // Create the add operation and add it into the work queue.
    AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp();
    AddOperationBasis addOp =
        new AddOperationBasis(this, nextOperationID.getAndIncrement(),
            message.getMessageID(), controls, protocolOp.getDN(),
            protocolOp.getAttributes());
    addOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, addOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      final Result result = Responses.newResult(de.getResultCode())
          .setDiagnosticMessage(de.getLocalizedMessage())
          .setMatchedDN(de.getMatchedDN().toString());
      for(String referral : de.getReferralURLs()) {
        result.addReferralURI(referral);
      }
      out.onNext(result);
      out.onComplete();
    }
    return connectionValid;
  }
  private void disconnectControlsNotAllowed()
  {
    disconnect(DisconnectReason.PROTOCOL_ERROR, false, ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
  }
  /**
   * Processes the provided LDAP message as a bind request.
   *
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the bind request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processBindRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final List<Control> controls, final FlowableEmitter<Response> out)
  {
    BindRequestProtocolOp protocolOp =
        message.getBindRequestProtocolOp();
    // See if this is an LDAPv2 bind request, and if so whether that
    // should be allowed.
    String versionString;
    switch (ldapVersion = protocolOp.getProtocolVersion())
    {
    case 2:
      versionString = "2";
      if (!connectionHandler.allowLDAPv2())
      {
        out.onNext(Responses.newBindResult(ResultCode.PROTOCOL_ERROR)
                            .setDiagnosticMessage(ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get().toString()));
        out.onComplete();
        disconnect(DisconnectReason.PROTOCOL_ERROR, false, ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
        return false;
      }
      if (!controls.isEmpty())
      {
        // LDAPv2 clients aren't allowed to send controls.
        out.onNext(Responses.newBindResult(ResultCode.PROTOCOL_ERROR)
            .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString()));
        out.onComplete();
        disconnectControlsNotAllowed();
        return false;
      }
      break;
    case 3:
      versionString = "3";
      break;
    default:
      // Unsupported protocol version. RFC4511 states that we MUST send
      // a protocol error back to the client.
      out.onNext(Responses.newBindResult(ResultCode.PROTOCOL_ERROR)
                          .setDiagnosticMessage(ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion).toString()));
      out.onComplete();
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
          ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion));
      return false;
    }
    ByteString bindDN = protocolOp.getDN();
    BindOperationBasis bindOp;
    switch (protocolOp.getAuthenticationType())
    {
    case SIMPLE:
      bindOp =
          new BindOperationBasis(this, nextOperationID
              .getAndIncrement(), message.getMessageID(), controls,
              versionString, bindDN, protocolOp.getSimplePassword());
      break;
    case SASL:
      bindOp =
          new BindOperationBasis(this, nextOperationID
              .getAndIncrement(), message.getMessageID(), controls,
              versionString, bindDN, protocolOp.getSASLMechanism(),
              protocolOp.getSASLCredentials());
      break;
    default:
      // This is an invalid authentication type, and therefore a
      // protocol error. As per RFC 2251, a protocol error in a bind
      // request must result in terminating the connection.
      LocalizableMessage msg =
          ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(),
              protocolOp.getAuthenticationType());
      disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
      return false;
    }
    // Add the operation into the work queue.
    bindOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, bindOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      final Result result = Responses.newBindResult(de.getResultCode())
                                                      .setDiagnosticMessage(de.getLocalizedMessage())
                                                      .setMatchedDN(de.getMatchedDN().toString());
      for(String referral : de.getReferralURLs())
      {
        result.addReferralURI(referral);
      }
      out.onNext(result);
      out.onComplete();
      // If it was a protocol error, then terminate the connection.
      if (de.getResultCode() == ResultCode.PROTOCOL_ERROR)
      {
        LocalizableMessage msg =
            ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(message
                .getMessageID(), de.getMessageObject());
        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
      }
    }
    return connectionValid;
  }
  /**
   * Processes the provided LDAP message as a compare request.
   *
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the compare request to
   *          process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processCompareRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final List<Control> controls, final FlowableEmitter<Response> out)
  {
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR)
                          .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString()));
      out.onComplete();
      disconnectControlsNotAllowed();
      return false;
    }
    CompareRequestProtocolOp protocolOp =
        message.getCompareRequestProtocolOp();
    CompareOperationBasis compareOp =
        new CompareOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getDN(), protocolOp.getAttributeType(),
            protocolOp.getAssertionValue());
    // Add the operation into the work queue.
    compareOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, compareOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      final CompareResult result = Responses.newCompareResult(de.getResultCode())
                                            .setDiagnosticMessage(de.getLocalizedMessage())
                                            .setMatchedDN(de.getMatchedDN().toString());
      result.getReferralURIs().addAll(de.getReferralURLs());
      out.onNext(result);
      out.onComplete();
    }
    return connectionValid;
  }
  /**
   * Processes the provided LDAP message as a delete request.
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the delete request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processDeleteRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final List<Control> controls, final FlowableEmitter<Response> out)
  {
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR)
                          .setDiagnosticMessage( ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString()));
      out.onComplete();
      disconnectControlsNotAllowed();
      return false;
    }
    DeleteRequestProtocolOp protocolOp =
        message.getDeleteRequestProtocolOp();
    DeleteOperationBasis deleteOp =
        new DeleteOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getDN());
    // Add the operation into the work queue.
    deleteOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, deleteOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      final Result result = Responses.newResult(de.getResultCode())
                                     .setDiagnosticMessage(de.getLocalizedMessage())
                                     .setMatchedDN(de.getMatchedDN().toString());
      result.getReferralURIs().addAll(de.getReferralURLs());
      out.onNext(result);
      out.onComplete();
    }
    return connectionValid;
  }
  /**
   * Processes the provided LDAP message as an extended request.
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the extended request to
   *          process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processExtendedRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
      final List<Control> controls, final FlowableEmitter<Response> out)
  {
    // See if this is an LDAPv2 client. If it is, then they should not
    // be issuing extended requests. We can't send a response that we
    // can be sure they can understand, so we have no choice but to
    // close the connection.
    if (ldapVersion == 2)
    {
      // LDAPv2 clients aren't allowed to send controls.
      LocalizableMessage msg = ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get(getConnectionID(), message.getMessageID());
      out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR)
                          .setDiagnosticMessage(msg.toString()));
      out.onComplete();
      logger.error(msg);
      disconnect(DisconnectReason.PROTOCOL_ERROR, false, msg);
      return false;
    }
    // FIXME -- Do we need to handle certain types of request here?
    // -- StartTLS requests
    // -- Cancel requests
    ExtendedRequestProtocolOp protocolOp =
        message.getExtendedRequestProtocolOp();
    ExtendedOperationBasis extendedOp =
        new ExtendedOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getOID(), protocolOp.getValue());
    // Add the operation into the work queue.
    extendedOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, extendedOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      final Result result = Responses.newResult(de.getResultCode())
                                     .setDiagnosticMessage(de.getMessage())
                                     .setMatchedDN(de.getMatchedDN().toString());
      result.getReferralURIs().addAll(de.getReferralURLs());
      out.onNext(result);
      out.onComplete();
    }
    return connectionValid;
  }
  /**
   * Processes the provided LDAP message as a modify request.
   *
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the modify request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processModifyRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final List<Control> controls, final FlowableEmitter<Response> out)
  {
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR)
                          .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString()));
      out.onComplete();
      disconnectControlsNotAllowed();
      return false;
    }
    ModifyRequestProtocolOp protocolOp =
        message.getModifyRequestProtocolOp();
    ModifyOperationBasis modifyOp =
        new ModifyOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getDN(), protocolOp.getModifications());
    // Add the operation into the work queue.
    modifyOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, modifyOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      final Result result =
          Responses.newResult(de.getResultCode()).setDiagnosticMessage(de.getMessage()).setMatchedDN(de.getMatchedDN()
              .toString());
      result.getReferralURIs().addAll(de.getReferralURLs());
      out.onNext(result);
      out.onComplete();
    }
    return connectionValid;
  }
  /**
   * Processes the provided LDAP message as a modify DN request.
   *
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the modify DN request to
   *          process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processModifyDNRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final List<Control> controls, final FlowableEmitter<Response> out)
  {
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR)
          .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString()));
      out.onComplete();
      disconnectControlsNotAllowed();
      return false;
    }
    ModifyDNRequestProtocolOp protocolOp =
        message.getModifyDNRequestProtocolOp();
    ModifyDNOperationBasis modifyDNOp =
        new ModifyDNOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getEntryDN(), protocolOp.getNewRDN(), protocolOp
                .deleteOldRDN(), protocolOp.getNewSuperior());
    // Add the operation into the work queue.
    modifyDNOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, modifyDNOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      final Result result =
          Responses.newResult(de.getResultCode()).setDiagnosticMessage(de.getMessage()).setMatchedDN(de.getMatchedDN()
              .toString());
      result.getReferralURIs().addAll(de.getReferralURLs());
      for(Control control : modifyDNOp.getResponseControls()) {
        result.addControl(Converters.from(control));
      }
      out.onNext(result);
      out.onComplete();
    }
    return connectionValid;
  }
  /**
   * Processes the provided LDAP message as a search request.
   *
   * @param queueingStrategy
   *          The {@link QueueingStrategy} to use for operation
   * @param message
   *          The LDAP message containing the search request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processSearchRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message,
          final List<Control> controls, final FlowableEmitter<Response> out)
  {
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR)
          .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString()));
      out.onComplete();
      disconnectControlsNotAllowed();
      return false;
    }
    SearchRequestProtocolOp protocolOp =
        message.getSearchRequestProtocolOp();
    SearchOperationBasis searchOp =
        new SearchOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getBaseDN(), protocolOp.getScope(), protocolOp
                .getDereferencePolicy(), protocolOp.getSizeLimit(),
            protocolOp.getTimeLimit(), protocolOp.getTypesOnly(),
            protocolOp.getFilter(), protocolOp.getAttributes());
    // Add the operation into the work queue.
    searchOp.setAttachment(REACTIVE_OUT, out);
    try
    {
      addOperationInProgress(queueingStrategy, searchOp);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      final Result result = Responses.newResult(de.getResultCode());
      if (de.getMessage() != null)
      {
        result.setDiagnosticMessage(de.getMessage());
      }
      if (de.getMatchedDN() != null)
      {
        result.setMatchedDN(de.getMatchedDN().toString());
      }
      if (de.getReferralURLs() != null)
      {
        result.getReferralURIs().addAll(de.getReferralURLs());
      }
      if (searchOp.getResponseControls() != null)
      {
        for (Control control : searchOp.getResponseControls())
        {
          result.addControl(Converters.from(control));
        }
      }
      out.onNext(result);
      out.onComplete();
    }
    return connectionValid;
  }
  /**
   * Processes the provided LDAP message as an unbind request.
   *
   * @param message
   *          The LDAP message containing the unbind request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processUnbindRequest(final LDAPMessage message, final List<Control> controls)
  {
    UnbindOperationBasis unbindOp =
        new UnbindOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls);
    unbindOp.run();
    // The client connection will never be valid after an unbind.
    return false;
  }
  @Override
  public String getMonitorSummary()
  {
    StringBuilder buffer = new StringBuilder();
    buffer.append("connID=\"");
    buffer.append(connectionID);
    buffer.append("\" connectTime=\"");
    buffer.append(getConnectTimeString());
    buffer.append("\" source=\"");
    buffer.append(clientAddress);
    buffer.append(":");
    buffer.append(clientPort);
    buffer.append("\" destination=\"");
    buffer.append(serverAddress);
    buffer.append(":");
    buffer.append(connectionHandler.getListeners().iterator().next().getPort());
    buffer.append("\" ldapVersion=\"");
    buffer.append(ldapVersion);
    buffer.append("\" authDN=\"");
    DN authDN = getAuthenticationInfo().getAuthenticationDN();
    if (authDN != null)
    {
      buffer.append(authDN);
    }
    buffer.append("\" security=\"");
    buffer.append("none");
    buffer.append("\" opsInProgress=\"");
    buffer.append(operationsInProgress.size());
    buffer.append("\"");
    int countPSearch = getPersistentSearches().size();
    if (countPSearch > 0)
    {
      buffer.append(" persistentSearches=\"");
      buffer.append(countPSearch);
      buffer.append("\"");
    }
    return buffer.toString();
  }
  /**
   * Appends a string representation of this client connection to the
   * provided buffer.
   *
   * @param buffer
   *          The buffer to which the information should be appended.
   */
  @Override
  public void toString(StringBuilder buffer)
  {
    buffer.append("LDAP client connection from ");
    buffer.append(clientAddress);
    buffer.append(":");
    buffer.append(clientPort);
    buffer.append(" to ");
    buffer.append(serverAddress);
    buffer.append(":");
    buffer.append(serverPort);
  }
  @Override
  public boolean prepareTLS(LocalizableMessageBuilder unavailableReason)
  {
    throw new UnsupportedOperationException();
  }
  /**
   * Retrieves the length of time in milliseconds that this client
   * connection has been idle. <BR>
   * <BR>
   * Note that the default implementation will always return zero.
   * Subclasses associated with connection handlers should override this
   * method if they wish to provided idle time limit functionality.
   *
   * @return The length of time in milliseconds that this client
   *         connection has been idle.
   */
  @Override
  public long getIdleTime()
  {
    if (operationsInProgress.isEmpty()
        && getPersistentSearches().isEmpty())
    {
      return TimeThread.getTime() - lastCompletionTime.get();
    }
    else
    {
      // There's at least one operation in progress, so it's not idle.
      return 0L;
    }
  }
  /**
   * Return the certificate chain array associated with a connection.
   *
   * @return The array of certificates associated with a connection.
   */
  public Certificate[] getClientCertificateChain()
  {
    return new Certificate[0];
  }
  @Override
  public int getSSF()
  {
    return 0;
  }
}
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
New file
@@ -0,0 +1,1116 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 */
package org.forgerock.opendj.reactive;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.config.server.ConfigurationChangeListener;
import org.forgerock.opendj.grizzly.GrizzlyLDAPListener;
import org.forgerock.opendj.ldap.AddressMask;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage;
import org.forgerock.opendj.server.config.server.ConnectionHandlerCfg;
import org.forgerock.opendj.server.config.server.LDAPConnectionHandlerCfg;
import org.forgerock.util.Function;
import org.forgerock.util.Options;
import org.opends.server.api.AlertGenerator;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConnectionHandler;
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.KeyManagerProvider;
import org.opends.server.api.ServerShutdownListener;
import org.opends.server.api.TrustManagerProvider;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.QueueingStrategy;
import org.opends.server.core.ServerContext;
import org.opends.server.core.WorkQueueStrategy;
import org.opends.server.extensions.NullKeyManagerProvider;
import org.opends.server.extensions.NullTrustManagerProvider;
import org.opends.server.monitors.ClientConnectionMonitorProvider;
import org.opends.server.protocols.ldap.LDAPStatistics;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.HostPort;
import org.opends.server.types.InitializationException;
import org.opends.server.types.SSLClientAuthPolicy;
import org.opends.server.util.SelectableCertificateKeyManager;
import org.opends.server.util.StaticUtils;
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.Single;
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.
 */
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 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();
    }
    // 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;
    }
    if (config.isAllowLDAPV2())
    {
      DirectoryServer.registerSupportedLDAPVersion(2, this);
    }
    else
    {
      DirectoryServer.deregisterSupportedLDAPVersion(2, this);
    }
    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;
    }
  }
  @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.
    }
  }
  /**
   * 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();
    }
    // 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);
        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());
        }
      }
      catch (IOException e)
      {
        logger.traceException(e);
        return ERR_CONNHANDLER_CANNOT_BIND.get("LDAP", configEntryDN, a.getHostAddress(), listenPort,
            getExceptionMessage(e));
      }
    }
    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;
    }
  }
  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 value) throws LdapException {
                        final LDAPClientConnection2 conn = canAccept(value);
                        return new ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>() {
                            @Override
                            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);
    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();
        }
        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;
        }
      }
    }
    // 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()) {
        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);
    }
  }
  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();
          }
        }
        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);
    }
  }
  /**
   * 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();
      }
    }
  }
}
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/ProxyToHandler.java
New file
@@ -0,0 +1,168 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.opendj.reactive;
import static com.forgerock.reactive.RxJavaStreams.*;
import static org.forgerock.util.Utils.closeSilently;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.requests.CompareRequest;
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
import org.forgerock.opendj.ldap.requests.Request;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.IntermediateResponse;
import org.forgerock.opendj.ldap.responses.Response;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
import org.forgerock.opendj.ldif.ChangeRecord;
import org.forgerock.util.promise.ExceptionHandler;
import org.forgerock.util.promise.ResultHandler;
import org.forgerock.util.promise.RuntimeExceptionHandler;
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.Single;
import com.forgerock.reactive.Stream;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableEmitter.BackpressureMode;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.functions.Action;
/** Forward {@link Request} to another server reached through the {@link LDAPConnectionFactory}. */
final class ProxyToHandler implements ReactiveHandler<LDAPClientConnection2, Request, Stream<Response>> {
    private final LDAPConnectionFactory connectionFactory;
    ProxyToHandler(final LDAPConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }
    @Override
    public Single<Stream<Response>> handle(final LDAPClientConnection2 context, final Request request) {
        return singleFromPublisher(Flowable.create(new FlowableOnSubscribe<Stream<Response>>() {
            @Override
            public void subscribe(final FlowableEmitter<Stream<Response>> emitter) throws Exception {
                final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
                connectionFactory.getConnectionAsync().thenOnResult(new ResultHandler<Connection>() {
                    @Override
                    public void handleResult(final Connection connection) {
                        connectionHolder.set(connection);
                        emitter.onNext(executeRequest(connection, request));
                        emitter.onComplete();
                    }
                }).thenOnException(new ExceptionHandler<LdapException>() {
                    @Override
                    public void handleException(LdapException exception) {
                        emitter.onError(exception);
                    }
                }).thenOnRuntimeException(new RuntimeExceptionHandler() {
                    @Override
                    public void handleRuntimeException(RuntimeException exception) {
                        emitter.onError(exception);
                    }
                });
            }
        }, BackpressureMode.ERROR));
    }
    private Stream<Response> executeRequest(final Connection connection, final Request request) {
        return streamFromPublisher(Flowable.create(new FlowableOnSubscribe<Response>() {
            @Override
            public void subscribe(FlowableEmitter<Response> emitter) throws Exception {
                // add/delete/modifyDN/modifyReq
                final PublisherAdaptor adapter = new PublisherAdaptor(emitter);
                if (request instanceof ChangeRecord) {
                    connection.applyChangeAsync((ChangeRecord) request, adapter).thenOnResult(adapter)
                            .thenOnException(adapter).thenOnRuntimeException(adapter);
                } else if (request instanceof CompareRequest) {
                    connection.compareAsync((CompareRequest) request, adapter).thenOnResult(adapter)
                            .thenOnException(adapter).thenOnRuntimeException(adapter);
                } else if (request instanceof ExtendedRequest) {
                    connection.compareAsync((CompareRequest) request, adapter).thenOnResult(adapter)
                            .thenOnException(adapter).thenOnRuntimeException(adapter);
                } else if (request instanceof SearchRequest) {
                    connection.searchAsync((SearchRequest) request, adapter).thenOnResult(adapter)
                            .thenOnException(adapter).thenOnRuntimeException(adapter);
                } else {
                    emitter.onError(new IllegalArgumentException("Unsupported request type"));
                }
            }
        }, BackpressureMode.ERROR).doAfterTerminate(new Action() {
            @Override
            public void run() throws Exception {
                closeSilently(connection);
            }
        }));
    }
    /** Adaptor forwarding events from the SDK handler to the emitter. */
    private final class PublisherAdaptor implements IntermediateResponseHandler, SearchResultHandler,
            ResultHandler<Result>, ExceptionHandler<Exception>, RuntimeExceptionHandler {
        private final FlowableEmitter<Response> emitter;
        PublisherAdaptor(final FlowableEmitter<Response> emitter) {
            this.emitter = emitter;
        }
        private boolean handle(final Response response) {
            emitter.onNext(response);
            return true;
        }
        @Override
        public boolean handleEntry(SearchResultEntry entry) {
            return handle(entry);
        }
        @Override
        public boolean handleReference(SearchResultReference reference) {
            return handle(reference);
        }
        @Override
        public boolean handleIntermediateResponse(IntermediateResponse response) {
            return handle(response);
        }
        @Override
        public void handleResult(Result result) {
            if (result != null) {
                handle(result);
            }
            emitter.onComplete();
        }
        @Override
        public void handleRuntimeException(RuntimeException exception) {
            emitter.onError(exception);
        }
        @Override
        public void handleException(Exception exception) {
            emitter.onError(exception);
        }
    }
}
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Route.java
New file
@@ -0,0 +1,61 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
package org.forgerock.opendj.reactive;
import static org.forgerock.util.Reject.checkNotNull;
import com.forgerock.opendj.util.Predicate;
import com.forgerock.reactive.ReactiveHandler;
/**
 * Contains a routing predicate and a handler. If the predicate matches, the handler will be invoked.
 *
 * @param <CTX>
 *            Type of context in which request are processed
 * @param <REQ>
 *            Type of routed request
 * @param <REP>
 *            Type of routed response
 */
public final class Route<CTX, REQ, REP> {
    final Predicate<REQ, CTX> predicate;
    final ReactiveHandler<CTX, REQ, REP> handler;
    /**
     * Creates a new route.
     *
     * @param <CTX>
     *            Type of context in which request are processed
     * @param <REQ>
     *            Type of routed request
     * @param <REP>
     *            Type of routed response
     * @param predicate
     *            The {@link Predicate} which must be fulfilled to apply this route
     * @param target
     *            The {@link ReactiveHandler} to invoke when this route is applied
     * @return a new {@link Route}
     */
    public static <CTX, REQ, REP> Route<CTX, REQ, REP> newRoute(final Predicate<REQ, CTX> predicate,
            final ReactiveHandler<CTX, REQ, REP> target) {
        return new Route<>(predicate, target);
    }
    private Route(final Predicate<REQ, CTX> predicate, final ReactiveHandler<CTX, REQ, REP> target) {
        this.predicate = checkNotNull(predicate, "predicate must not be null");
        this.handler = checkNotNull(target, "handler must not be null");
    }
}
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/package-info.java
New file
@@ -0,0 +1,19 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 */
/** Classes implementing a Grizzly based {@link org.opends.server.api.ConnectionHandler} using reactive api. */
package org.forgerock.opendj.reactive;
opendj-server-legacy/src/main/java/org/opends/server/core/LdapEndpointConfigManager.java
New file
@@ -0,0 +1,798 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 */
package org.opends.server.core;
import static org.forgerock.opendj.ldap.ResultCode.*;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.core.DirectoryServer.*;
import static org.opends.server.util.StaticUtils.*;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.config.server.ConfigurationAddListener;
import org.forgerock.opendj.config.server.ConfigurationChangeListener;
import org.forgerock.opendj.config.server.ConfigurationDeleteListener;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.server.config.meta.BackendCfgDefn;
import org.forgerock.opendj.server.config.server.BackendCfg;
import org.forgerock.opendj.server.config.server.RootCfg;
import org.opends.server.api.Backend;
import org.opends.server.api.BackendInitializationListener;
import org.opends.server.backends.ConfigurationBackend;
import org.opends.server.config.ConfigConstants;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.WritabilityMode;
/**
 * This class defines a utility that will be used to manage the configuration
 * for the set of backends defined in the Directory Server.  It will perform
 * the necessary initialization of those backends when the server is first
 * started, and then will manage any changes to them while the server is
 * running.
 */
public class LdapEndpointConfigManager implements
     ConfigurationChangeListener<BackendCfg>,
     ConfigurationAddListener<BackendCfg>,
     ConfigurationDeleteListener<BackendCfg>
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** The mapping between configuration entry DNs and their corresponding backend implementations. */
  private final ConcurrentHashMap<DN, Backend<? extends BackendCfg>> registeredBackends = new ConcurrentHashMap<>();
  private final ServerContext serverContext;
  /**
   * Creates a new instance of this backend config manager.
   *
   * @param serverContext
   *            The server context.
   */
  public LdapEndpointConfigManager(ServerContext serverContext)
  {
    this.serverContext = serverContext;
  }
  /**
   * Initializes the configuration associated with the Directory Server
   * backends. This should only be called at Directory Server startup.
   *
   * @param backendIDsToStart
   *           The list of backendID to start. Everything will be started if empty.
   * @throws ConfigException
   *           If a critical configuration problem prevents the backend
   *           initialization from succeeding.
   * @throws InitializationException
   *           If a problem occurs while initializing the backends that is not
   *           related to the server configuration.
   */
  public void initializeBackendConfig(Collection<String> backendIDsToStart)
         throws ConfigException, InitializationException
  {
    initializeConfigurationBackend();
    // Register add and delete listeners.
    RootCfg root = serverContext.getRootConfig();
    root.addBackendAddListener(this);
    root.addBackendDeleteListener(this);
    // Get the configuration entry that is at the root of all the backends in
    // the server.
    Entry backendRoot;
    try
    {
      DN configEntryDN = DN.valueOf(ConfigConstants.DN_BACKEND_BASE);
      backendRoot   = DirectoryServer.getConfigEntry(configEntryDN);
    }
    catch (Exception e)
    {
      logger.traceException(e);
      LocalizableMessage message =
          ERR_CONFIG_BACKEND_CANNOT_GET_CONFIG_BASE.get(getExceptionMessage(e));
      throw new ConfigException(message, e);
    }
    // If the configuration root entry is null, then assume it doesn't exist.
    // In that case, then fail.  At least that entry must exist in the
    // configuration, even if there are no backends defined below it.
    if (backendRoot == null)
    {
      throw new ConfigException(ERR_CONFIG_BACKEND_BASE_DOES_NOT_EXIST.get());
    }
    initializeBackends(backendIDsToStart, root);
  }
  /**
   * Initializes specified backends. If a backend has been already initialized, do nothing.
   * This should only be called at Directory Server startup, after #initializeBackendConfig()
   *
   * @param backendIDsToStart
   *           The list of backendID to start. Everything will be started if empty.
   * @param root
   *           The configuration of the server's Root backend
   * @throws ConfigException
   *           If a critical configuration problem prevents the backend
   *           initialization from succeeding.
   */
  public void initializeBackends(Collection<String> backendIDsToStart, RootCfg root) throws ConfigException
  {
    // Initialize existing backends.
    for (String name : root.listBackends())
    {
      // Get the handler's configuration.
      // This will decode and validate its properties.
      final BackendCfg backendCfg = root.getBackend(name);
      final String backendID = backendCfg.getBackendId();
      if (!backendIDsToStart.isEmpty() && !backendIDsToStart.contains(backendID))
      {
        continue;
      }
      if (DirectoryServer.hasBackend(backendID))
      {
        // Skip this backend if it is already initialized and registered as available.
        continue;
      }
      // Register as a change listener for this backend so that we can be
      // notified when it is disabled or enabled.
      backendCfg.addChangeListener(this);
      final DN backendDN = backendCfg.dn();
      if (!backendCfg.isEnabled())
      {
        logger.debug(INFO_CONFIG_BACKEND_DISABLED, backendDN);
        continue;
      }
      // See if the entry contains an attribute that specifies the class name
      // for the backend implementation.  If it does, then load it and make
      // sure that it's a valid backend implementation.  There is no such
      // attribute, the specified class cannot be loaded, or it does not
      // contain a valid backend implementation, then log an error and skip it.
      String className = backendCfg.getJavaClass();
      Backend<? extends BackendCfg> backend;
      try
      {
        backend = loadBackendClass(className).newInstance();
      }
      catch (Exception e)
      {
        logger.traceException(e);
        logger.error(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE, className, backendDN, stackTraceToSingleLineString(e));
        continue;
      }
      initializeBackend(backend, backendCfg);
    }
  }
  private void initializeConfigurationBackend() throws InitializationException
  {
    final ConfigurationBackend configBackend =
        new ConfigurationBackend(serverContext, DirectoryServer.getConfigurationHandler());
    initializeBackend(configBackend, configBackend.getBackendCfg());
  }
  private void initializeBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg)
  {
    ConfigChangeResult ccr = new ConfigChangeResult();
    initializeBackend(backend, backendCfg, ccr);
    for (LocalizableMessage msg : ccr.getMessages())
    {
      logger.error(msg);
    }
  }
  private void initializeBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg, ConfigChangeResult ccr)
  {
    backend.setBackendID(backendCfg.getBackendId());
    backend.setWritabilityMode(toWritabilityMode(backendCfg.getWritabilityMode()));
    if (acquireSharedLock(backend, backendCfg.getBackendId(), ccr) && configureAndOpenBackend(backend, backendCfg, ccr))
    {
      registerBackend(backend, backendCfg, ccr);
    }
  }
  /**
   * Acquire a shared lock on this backend. This will prevent operations like LDIF import or restore
   * from occurring while the backend is active.
   */
  private boolean acquireSharedLock(Backend<?> backend, String backendID, final ConfigChangeResult ccr)
  {
    try
    {
      String lockFile = LockFileManager.getBackendLockFileName(backend);
      StringBuilder failureReason = new StringBuilder();
      if (!LockFileManager.acquireSharedLock(lockFile, failureReason))
      {
        cannotAcquireLock(backendID, ccr, failureReason);
        return false;
      }
      return true;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      cannotAcquireLock(backendID, ccr, stackTraceToSingleLineString(e));
      return false;
    }
  }
  private void cannotAcquireLock(String backendID, final ConfigChangeResult ccr, CharSequence failureReason)
  {
    LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID, failureReason);
    logger.error(message);
    // FIXME -- Do we need to send an admin alert?
    ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
    ccr.setAdminActionRequired(true);
    ccr.addMessage(message);
  }
  private void releaseSharedLock(Backend<?> backend, String backendID)
  {
    try
    {
      String lockFile = LockFileManager.getBackendLockFileName(backend);
      StringBuilder failureReason = new StringBuilder();
      if (! LockFileManager.releaseLock(lockFile, failureReason))
      {
        logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, failureReason);
        // FIXME -- Do we need to send an admin alert?
      }
    }
    catch (Exception e2)
    {
      logger.traceException(e2);
      logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, stackTraceToSingleLineString(e2));
      // FIXME -- Do we need to send an admin alert?
    }
  }
  @Override
  public boolean isConfigurationChangeAcceptable(
       BackendCfg configEntry,
       List<LocalizableMessage> unacceptableReason)
  {
    DN backendDN = configEntry.dn();
    Set<DN> baseDNs = configEntry.getBaseDN();
    // See if the backend is registered with the server.  If it is, then
    // see what's changed and whether those changes are acceptable.
    Backend<?> backend = registeredBackends.get(backendDN);
    if (backend != null)
    {
      LinkedHashSet<DN> removedDNs = new LinkedHashSet<>(backend.getBaseDNs());
      LinkedHashSet<DN> addedDNs = new LinkedHashSet<>(baseDNs);
      Iterator<DN> iterator = removedDNs.iterator();
      while (iterator.hasNext())
      {
        DN dn = iterator.next();
        if (addedDNs.remove(dn))
        {
          iterator.remove();
        }
      }
      // Copy the directory server's base DN registry and make the
      // requested changes to see if it complains.
      BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry();
      for (DN dn : removedDNs)
      {
        try
        {
          reg.deregisterBaseDN(dn);
        }
        catch (DirectoryException de)
        {
          logger.traceException(de);
          unacceptableReason.add(de.getMessageObject());
          return false;
        }
      }
      for (DN dn : addedDNs)
      {
        try
        {
          reg.registerBaseDN(dn, backend, false);
        }
        catch (DirectoryException de)
        {
          logger.traceException(de);
          unacceptableReason.add(de.getMessageObject());
          return false;
        }
      }
    }
    else if (configEntry.isEnabled())
    {
      /*
       * If the backend was not enabled, it has not been registered with directory server, so
       * no listeners will be registered at the lower layers. Verify as it was an add.
       */
      String className = configEntry.getJavaClass();
      try
      {
        Class<Backend<BackendCfg>> backendClass = loadBackendClass(className);
        if (! Backend.class.isAssignableFrom(backendClass))
        {
          unacceptableReason.add(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
          return false;
        }
        Backend<BackendCfg> b = backendClass.newInstance();
        if (! b.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext))
        {
          return false;
        }
      }
      catch (Exception e)
      {
        logger.traceException(e);
        unacceptableReason.add(
            ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(className, backendDN, stackTraceToSingleLineString(e)));
        return false;
      }
    }
    // If we've gotten to this point, then it is acceptable as far as we are
    // concerned.  If it is unacceptable according to the configuration for that
    // backend, then the backend itself will need to make that determination.
    return true;
  }
  @Override
  public ConfigChangeResult applyConfigurationChange(BackendCfg cfg)
  {
    DN backendDN = cfg.dn();
    Backend<? extends BackendCfg> backend = registeredBackends.get(backendDN);
    final ConfigChangeResult ccr = new ConfigChangeResult();
    // See if the entry contains an attribute that indicates whether the
    // backend should be enabled.
    boolean needToEnable = false;
    try
    {
      if (cfg.isEnabled())
      {
        // The backend is marked as enabled.  See if that is already true.
        if (backend == null)
        {
          needToEnable = true;
        } // else already enabled, no need to do anything.
      }
      else
      {
        // The backend is marked as disabled.  See if that is already true.
        if (backend != null)
        {
          // It isn't disabled, so we will do so now and deregister it from the
          // Directory Server.
          deregisterBackend(backendDN, backend);
          backend.finalizeBackend();
          releaseSharedLock(backend, backend.getBackendID());
          return ccr;
        } // else already disabled, no need to do anything.
      }
    }
    catch (Exception e)
    {
      logger.traceException(e);
      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
      ccr.addMessage(ERR_CONFIG_BACKEND_UNABLE_TO_DETERMINE_ENABLED_STATE.get(backendDN,
          stackTraceToSingleLineString(e)));
      return ccr;
    }
    // See if the entry contains an attribute that specifies the class name
    // for the backend implementation.  If it does, then load it and make sure
    // that it's a valid backend implementation.  There is no such attribute,
    // the specified class cannot be loaded, or it does not contain a valid
    // backend implementation, then log an error and skip it.
    String className = cfg.getJavaClass();
    // See if this backend is currently active and if so if the name of the class is the same.
    if (backend != null && !className.equals(backend.getClass().getName()))
    {
      // It is not the same. Try to load it and see if it is a valid backend implementation.
      try
      {
        Class<?> backendClass = DirectoryServer.loadClass(className);
        if (Backend.class.isAssignableFrom(backendClass))
        {
          // It appears to be a valid backend class.  We'll return that the
          // change is successful, but indicate that some administrative
          // action is required.
          ccr.addMessage(NOTE_CONFIG_BACKEND_ACTION_REQUIRED_TO_CHANGE_CLASS.get(
              backendDN, backend.getClass().getName(), className));
          ccr.setAdminActionRequired(true);
        }
        else
        {
          // It is not a valid backend class.  This is an error.
          ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
          ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
        }
        return ccr;
      }
      catch (Exception e)
      {
        logger.traceException(e);
        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
        ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
                className, backendDN, stackTraceToSingleLineString(e)));
        return ccr;
      }
    }
    // If we've gotten here, then that should mean that we need to enable the
    // backend.  Try to do so.
    if (needToEnable)
    {
      try
      {
        backend = loadBackendClass(className).newInstance();
      }
      catch (Exception e)
      {
        // It is not a valid backend class.  This is an error.
        ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
        ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
        return ccr;
      }
      initializeBackend(backend, cfg, ccr);
      return ccr;
    }
    else if (ccr.getResultCode() == ResultCode.SUCCESS && backend != null)
    {
      backend.setWritabilityMode(toWritabilityMode(cfg.getWritabilityMode()));
    }
    return ccr;
  }
  private boolean registerBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg, ConfigChangeResult ccr)
  {
    for (BackendInitializationListener listener : getBackendInitializationListeners())
    {
      listener.performBackendPreInitializationProcessing(backend);
    }
    try
    {
      DirectoryServer.registerBackend(backend);
    }
    catch (Exception e)
    {
      logger.traceException(e);
      LocalizableMessage message =
          WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(backendCfg.getBackendId(), getExceptionMessage(e));
      logger.error(message);
      // FIXME -- Do we need to send an admin alert?
      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
      ccr.addMessage(message);
      return false;
    }
    for (BackendInitializationListener listener : getBackendInitializationListeners())
    {
      listener.performBackendPostInitializationProcessing(backend);
    }
    registeredBackends.put(backendCfg.dn(), backend);
    return true;
  }
  @Override
  public boolean isConfigurationAddAcceptable(
       BackendCfg configEntry,
       List<LocalizableMessage> unacceptableReason)
  {
    DN backendDN = configEntry.dn();
    // See if the entry contains an attribute that specifies the backend ID.  If
    // it does not, then skip it.
    String backendID = configEntry.getBackendId();
    if (DirectoryServer.hasBackend(backendID))
    {
      unacceptableReason.add(WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID));
      return false;
    }
    // See if the entry contains an attribute that specifies the set of base DNs
    // for the backend.  If it does not, then skip it.
    Set<DN> baseList = configEntry.getBaseDN();
    DN[] baseDNs = new DN[baseList.size()];
    baseList.toArray(baseDNs);
    // See if the entry contains an attribute that specifies the class name
    // for the backend implementation.  If it does, then load it and make sure
    // that it's a valid backend implementation.  There is no such attribute,
    // the specified class cannot be loaded, or it does not contain a valid
    // backend implementation, then log an error and skip it.
    String className = configEntry.getJavaClass();
    Backend<BackendCfg> backend;
    try
    {
      backend = loadBackendClass(className).newInstance();
    }
    catch (Exception e)
    {
      logger.traceException(e);
      unacceptableReason.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
              className, backendDN, stackTraceToSingleLineString(e)));
      return false;
    }
    // Make sure that all of the base DNs are acceptable for use in the server.
    BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry();
    for (DN baseDN : baseDNs)
    {
      try
      {
        reg.registerBaseDN(baseDN, backend, false);
      }
      catch (DirectoryException de)
      {
        unacceptableReason.add(de.getMessageObject());
        return false;
      }
      catch (Exception e)
      {
        unacceptableReason.add(getExceptionMessage(e));
        return false;
      }
    }
    return backend.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext);
  }
  @Override
  public ConfigChangeResult applyConfigurationAdd(BackendCfg cfg)
  {
    DN                backendDN           = cfg.dn();
    final ConfigChangeResult ccr = new ConfigChangeResult();
    // Register as a change listener for this backend entry so that we will
    // be notified of any changes that may be made to it.
    cfg.addChangeListener(this);
    // See if the entry contains an attribute that indicates whether the backend should be enabled.
    // If it does not, or if it is not set to "true", then skip it.
    if (!cfg.isEnabled())
    {
      // The backend is explicitly disabled.  We will log a message to
      // indicate that it won't be enabled and return.
      LocalizableMessage message = INFO_CONFIG_BACKEND_DISABLED.get(backendDN);
      logger.debug(message);
      ccr.addMessage(message);
      return ccr;
    }
    // See if the entry contains an attribute that specifies the backend ID.  If
    // it does not, then skip it.
    String backendID = cfg.getBackendId();
    if (DirectoryServer.hasBackend(backendID))
    {
      LocalizableMessage message = WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID);
      logger.warn(message);
      ccr.addMessage(message);
      return ccr;
    }
    // See if the entry contains an attribute that specifies the class name
    // for the backend implementation.  If it does, then load it and make sure
    // that it's a valid backend implementation.  There is no such attribute,
    // the specified class cannot be loaded, or it does not contain a valid
    // backend implementation, then log an error and skip it.
    String className = cfg.getJavaClass();
    Backend<? extends BackendCfg> backend;
    try
    {
      backend = loadBackendClass(className).newInstance();
    }
    catch (Exception e)
    {
      logger.traceException(e);
      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
      ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
          className, backendDN, stackTraceToSingleLineString(e)));
      return ccr;
    }
    initializeBackend(backend, cfg, ccr);
    return ccr;
  }
  private boolean configureAndOpenBackend(Backend<?> backend, BackendCfg cfg, ConfigChangeResult ccr)
  {
    try
    {
      configureAndOpenBackend(backend, cfg);
      return true;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
      ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INITIALIZE.get(
          cfg.getJavaClass(), cfg.dn(), stackTraceToSingleLineString(e)));
      releaseSharedLock(backend, cfg.getBackendId());
      return false;
    }
  }
  @SuppressWarnings({ "unchecked", "rawtypes" })
  private void configureAndOpenBackend(Backend backend, BackendCfg cfg) throws ConfigException, InitializationException
  {
    backend.configureBackend(cfg, serverContext);
    backend.openBackend();
  }
  @SuppressWarnings("unchecked")
  private Class<Backend<BackendCfg>> loadBackendClass(String className) throws Exception
  {
    return (Class<Backend<BackendCfg>>) DirectoryServer.loadClass(className);
  }
  private WritabilityMode toWritabilityMode(BackendCfgDefn.WritabilityMode writabilityMode)
  {
    switch (writabilityMode)
    {
    case DISABLED:
      return WritabilityMode.DISABLED;
    case ENABLED:
      return WritabilityMode.ENABLED;
    case INTERNAL_ONLY:
      return WritabilityMode.INTERNAL_ONLY;
    default:
      return WritabilityMode.ENABLED;
    }
  }
  @Override
  public boolean isConfigurationDeleteAcceptable(
       BackendCfg configEntry,
       List<LocalizableMessage> unacceptableReason)
  {
    DN backendDN = configEntry.dn();
    // See if this backend config manager has a backend registered with the
    // provided DN.  If not, then we don't care if the entry is deleted.  If we
    // do know about it, then that means that it is enabled and we will not
    // allow removing a backend that is enabled.
    Backend<?> backend = registeredBackends.get(backendDN);
    if (backend == null)
    {
      return true;
    }
    // See if the backend has any subordinate backends.  If so, then it is not
    // acceptable to remove it.  Otherwise, it should be fine.
    Backend<?>[] subBackends = backend.getSubordinateBackends();
    if (subBackends != null && subBackends.length != 0)
    {
      unacceptableReason.add(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN));
      return false;
    }
    return true;
  }
  @Override
  public ConfigChangeResult applyConfigurationDelete(BackendCfg configEntry)
  {
    DN                backendDN           = configEntry.dn();
    final ConfigChangeResult ccr = new ConfigChangeResult();
    // See if this backend config manager has a backend registered with the
    // provided DN.  If not, then we don't care if the entry is deleted.
    Backend<?> backend = registeredBackends.get(backendDN);
    if (backend == null)
    {
      return ccr;
    }
    // See if the backend has any subordinate backends.  If so, then it is not
    // acceptable to remove it.  Otherwise, it should be fine.
    Backend<?>[] subBackends = backend.getSubordinateBackends();
    if (subBackends != null && subBackends.length > 0)
    {
      ccr.setResultCode(UNWILLING_TO_PERFORM);
      ccr.addMessage(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN));
      return ccr;
    }
    deregisterBackend(backendDN, backend);
    try
    {
      backend.finalizeBackend();
    }
    catch (Exception e)
    {
      logger.traceException(e);
    }
    configEntry.removeChangeListener(this);
    releaseSharedLock(backend, backend.getBackendID());
    return ccr;
  }
  private void deregisterBackend(DN backendDN, Backend<?> backend)
  {
    for (BackendInitializationListener listener : getBackendInitializationListeners())
    {
      listener.performBackendPreFinalizationProcessing(backend);
    }
    registeredBackends.remove(backendDN);
    DirectoryServer.deregisterBackend(backend);
    for (BackendInitializationListener listener : getBackendInitializationListeners())
    {
      listener.performBackendPostFinalizationProcessing(backend);
    }
  }
}