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

Gaetan Boismal
15.50.2014 45141fb11ef698b11c6fb3becca82ca10e11505a
OPENDJ-1285 CR-4409 Migrate SDK from Futures to Promises

Connection, ConnectionFactory, ResultHandler and SearchResultHandler public APIs have been changed.
* Connection.java
In all xxxAsync method, the final handler parameter will be removed and the client is expected to register the handler with the returned promise. For search request, a SearchResultHandler is still required because it needs to handle search result entries/references.

* ConnectionFactory.java
In the method getConnectionAsync(), the final handler parameter will be removed.

* ResultHandler.java
Now extends SuccessHandler and FailureHandler in order to allow usage of then() method call.

* SearchResultHandler.java
Remove the inheritance link with ResultHandler. So client needs to register a ResultHandler instance when he calls searchAsync in addition to register a SearchResultHandler.
5 files deleted
2 files added
70 files modified
8372 ■■■■■ changed files
opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java 165 ●●●●● patch | view | raw | blame | history
opendj-core/clirr-ignored-api-changes.xml 56 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java 421 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/opendj/util/CompletedFutureResult.java 157 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/opendj/util/FutureResultTransformer.java 192 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/opendj/util/RecursiveFutureResult.java 221 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnection.java 118 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java 458 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractConnectionWrapper.java 179 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractLoadBalancingAlgorithm.java 72 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractSynchronousConnection.java 82 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java 105 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/CachedConnectionPool.java 229 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/Connection.java 282 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/ConnectionFactory.java 17 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/ConnectionPool.java 18 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java 12 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/FutureResult.java 87 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/FutureResultImpl.java 64 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/FutureResultWrapper.java 281 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java 533 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/InternalConnection.java 75 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/InternalConnectionFactory.java 25 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java 8 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/LoadBalancer.java 16 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java 62 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/RequestHandler.java 13 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/RequestHandlerFactoryAdapter.java 50 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/ResultHandler.java 9 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/RootDSE.java 29 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/SearchResultHandler.java 5 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/responses/AbstractExtendedResultDecoder.java 8 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java 66 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java 263 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLDAPFutureResultImpl.java 72 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPBindFutureResultImpl.java 11 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPCompareFutureResultImpl.java 12 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPExtendedFutureResultImpl.java 14 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPFutureResultImpl.java 8 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPSearchFutureResultImpl.java 12 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldif/ConnectionEntryReader.java 17 ●●●● patch | view | raw | blame | history
opendj-core/src/test/java/com/forgerock/opendj/util/FutureResultTransformerTestCase.java 132 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnectionTestCase.java 299 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/AbstractLoadBalancingAlgorithmTestCase.java 36 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/ConnectionPoolTestCase.java 58 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java 158 ●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java 55 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/TestCaseUtils.java 39 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SchemaBuilderTestCase.java 75 ●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SchemaTestCase.java 62 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPConnectionFactory.java 21 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldif/ConnectionEntryReaderTestCase.java 60 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java 165 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java 79 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java 20 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java 144 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java 187 ●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java 11 ●●●●● patch | view | raw | blame | history
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java 34 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java 10 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ProxyBackend.java 383 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java 44 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java 175 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AddRate.java 29 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AuthRate.java 78 ●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java 25 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/ModRate.java 20 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java 34 ●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/SearchRate.java 18 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java 192 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java 117 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java 762 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java 270 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java 10 ●●●● patch | view | raw | blame | history
opendj-server2x-adapter/src/main/java/org/forgerock/opendj/adapter/server2x/Adapters.java 23 ●●●●● patch | view | raw | blame | history
opendj-server3x-adapter/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java 21 ●●●●● patch | view | raw | blame | history
pom.xml 2 ●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java
@@ -26,20 +26,24 @@
 */
package com.forgerock.opendj.cli;
import org.forgerock.opendj.ldap.Connection;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.opendj.ldap.AbstractConnectionWrapper;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.Function;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.SuccessHandler;
import com.forgerock.opendj.util.FutureResultTransformer;
import com.forgerock.opendj.util.RecursiveFutureResult;
import static org.forgerock.util.Utils.*;
/**
 * An authenticated connection factory can be used to create pre-authenticated
 * connections to a Directory Server.
@@ -83,19 +87,21 @@
         * These methods will always throw {@code UnsupportedOperationException}.
         */
        /** {@inheritDoc} */
        @Override
        public BindResult bind(BindRequest request) throws ErrorResultException {
            throw new UnsupportedOperationException();
        }
        /** {@inheritDoc} */
        @Override
        public BindResult bind(String name, char[] password) throws ErrorResultException {
            throw new UnsupportedOperationException();
        }
        /** {@inheritDoc} */
        @Override
        public FutureResult<BindResult> bindAsync(BindRequest request,
                IntermediateResponseHandler intermediateResponseHandler,
                ResultHandler<? super BindResult> resultHandler) {
            IntermediateResponseHandler intermediateResponseHandler) {
            throw new UnsupportedOperationException();
        }
@@ -115,10 +121,6 @@
         * associated with this connection. If re-authentication fails for some
         * reason then this connection will be automatically closed.
         *
         * @param handler
         *            A result handler which can be used to asynchronously
         *            process the operation result when it is received, may be
         *            {@code null}.
         * @return A future representing the result of the operation.
         * @throws UnsupportedOperationException
         *             If this connection does not support rebind operations.
@@ -126,45 +128,27 @@
         *             If this connection has already been closed, i.e. if
         *             {@code isClosed() == true}.
         */
        public FutureResult<BindResult> rebindAsync(final ResultHandler<? super BindResult> handler) {
        public FutureResult<BindResult> rebindAsync() {
            if (request == null) {
                throw new UnsupportedOperationException();
            }
            /*
             * Wrap the client handler so that we can update the connection
             * state.
             */
            final ResultHandler<? super BindResult> clientHandler = handler;
            final ResultHandler<BindResult> handlerWrapper = new ResultHandler<BindResult>() {
                /** {@inheritDoc} */
                public void handleErrorResult(final ErrorResultException error) {
                    /*
                     * This connection is now unauthenticated so prevent further
                     * use.
                     */
                    connection.close();
                    if (clientHandler != null) {
                        clientHandler.handleErrorResult(error);
                    }
                }
                /** {@inheritDoc} */
                public void handleResult(final BindResult result) {
                    // Save the result.
                    AuthenticatedConnection.this.result = result;
                    if (clientHandler != null) {
                        clientHandler.handleResult(result);
                    }
                }
            };
            return connection.bindAsync(request, null, handlerWrapper);
            return (FutureResult<BindResult>) connection.bindAsync(request)
                    .onSuccess(new SuccessHandler<BindResult>() {
                        @Override
                        public void handleResult(final BindResult result) {
                            // Save the result.
                            AuthenticatedConnection.this.result = result;
                        }
                    }).onFailure(new FailureHandler<ErrorResultException>() {
                        @Override
                        public void handleError(final ErrorResultException error) {
                            /*
                             * This connection is now unauthenticated so prevent further use.
                             */
                            connection.close();
                        }
                    });
        }
        /**
@@ -172,6 +156,7 @@
         *
         * @return The string representation of this authenticated connection factory.
         */
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("AuthenticatedConnection(");
@@ -182,56 +167,9 @@
    }
    private static final class FutureResultImpl {
        private final FutureResultTransformer<BindResult, Connection> futureBindResult;
        private final RecursiveFutureResult<Connection, BindResult> futureConnectionResult;
        private final BindRequest bindRequest;
        private Connection connection;
        private FutureResultImpl(final BindRequest request,
                final ResultHandler<? super Connection> handler) {
            this.bindRequest = request;
            this.futureBindResult = new FutureResultTransformer<BindResult, Connection>(handler) {
                @Override
                protected ErrorResultException transformErrorResult(
                        final ErrorResultException errorResult) {
                    // Ensure that the connection is closed.
                    if (connection != null) {
                        connection.close();
                        connection = null;
                    }
                    return errorResult;
                }
                @Override
                protected AuthenticatedConnection transformResult(final BindResult result)
                        throws ErrorResultException {
                    // FIXME: should make the result unmodifiable.
                    return new AuthenticatedConnection(connection, bindRequest, result);
                }
            };
            this.futureConnectionResult =
                    new RecursiveFutureResult<Connection, BindResult>(futureBindResult) {
                        @Override
                        protected FutureResult<? extends BindResult> chainResult(
                                final Connection innerResult,
                                final ResultHandler<? super BindResult> handler)
                                throws ErrorResultException {
                            connection = innerResult;
                            return connection.bindAsync(bindRequest, null, handler);
                        }
                    };
            futureBindResult.setFutureResult(futureConnectionResult);
        }
    }
    private final BindRequest request;
    private final ConnectionFactory parentFactory;
    private boolean allowRebinds = false;
    private boolean allowRebinds;
    /**
     * Creates a new authenticated connection factory which will obtain
@@ -260,6 +198,7 @@
    }
    /** {@inheritDoc} */
    @Override
    public Connection getConnection() throws ErrorResultException {
        final Connection connection = parentFactory.getConnection();
        BindResult bindResult = null;
@@ -279,12 +218,35 @@
    }
    /** {@inheritDoc} */
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> handler) {
        final FutureResultImpl future = new FutureResultImpl(request, handler);
        future.futureConnectionResult.setFutureResult(parentFactory
                .getConnectionAsync(future.futureConnectionResult));
        return future.futureBindResult;
    @Override
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
        return parentFactory.getConnectionAsync()
            .thenAsync(
                    new AsyncFunction<Connection, BindResult, ErrorResultException>() {
                        @Override
                        public Promise<BindResult, ErrorResultException> apply(final Connection connection)
                                throws ErrorResultException {
                            connectionHolder.set(connection);
                            return connection.bindAsync(request);
                        }
                    }
            ).then(
                    new Function<BindResult, Connection, ErrorResultException>() {
                        @Override
                        public Connection apply(BindResult result) throws ErrorResultException {
                            // FIXME: should make the result unmodifiable.
                            return new AuthenticatedConnection(connectionHolder.get(), request, result);
                        }
                    },
                    new Function<ErrorResultException, Connection, ErrorResultException>() {
                        @Override
                        public Connection apply(ErrorResultException errorResult) throws ErrorResultException {
                            closeSilently(connectionHolder.get());
                            throw errorResult;
                        }
                    }
            );
    }
    /**
@@ -325,10 +287,11 @@
     *
     * @return The string representation of this authenticated connection factory.
     */
    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("AuthenticatedConnectionFactory(");
        builder.append(String.valueOf(parentFactory));
        builder.append(parentFactory);
        builder.append(')');
        return builder.toString();
    }
opendj-core/clirr-ignored-api-changes.xml
@@ -222,5 +222,59 @@
    <method>boolean isIndexingSupported()</method>
    <justification>OPENDJ-1308 Migrate schema support: allows decoupling indexing from a specific backend</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/*Connection*</className>
    <differenceType>7004</differenceType>
    <method>org.forgerock.opendj.ldap.FutureResult *Async(*org.forgerock.opendj.ldap.ResultHandler)</method>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/schema/Schema</className>
    <differenceType>7004</differenceType>
    <method>org.forgerock.opendj.ldap.FutureResult readSchema*Async*(org.forgerock.opendj.ldap.Connection, org.forgerock.opendj.ldap.DN, org.forgerock.opendj.ldap.ResultHandler)</method>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/*Connection*</className>
    <differenceType>7006</differenceType>
    <method>org.forgerock.opendj.ldap.FutureResult *Async(*org.forgerock.opendj.ldap.ResultHandler)</method>
    <to>org.forgerock.util.promise.Promise</to>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/Connection</className>
    <differenceType>7012</differenceType>
    <method>org.forgerock.opendj.ldap.FutureResult *Async(org.forgerock.opendj.*)</method>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
  <difference>
    <className>%regex[org/forgerock/opendj/ldap/(RequestHandler|MemoryBackend)]</className>
    <differenceType>7004</differenceType>
    <method>*handleSearch(*)</method>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/ResultHandler</className>
    <differenceType>7012</differenceType>
    <method>*handleError(org.forgerock.opendj.ldap.ErrorResultException)</method>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/ResultHandler</className>
    <differenceType>7002</differenceType>
    <method>*handleErrorResult(org.forgerock.opendj.ldap.ErrorResultException)</method>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/SearchResultHandler</className>
    <differenceType>4001</differenceType>
    <to>org/forgerock/opendj/ldap/ResultHandler</to>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/schema/SchemaBuilder</className>
    <differenceType>7004</differenceType>
    <method>org.forgerock.opendj.ldap.FutureResult addSchema*Async(*)</method>
    <justification>OPENDJ-1285 Migrate SDK from Futures to Promises</justification>
  </difference>
</differences>
opendj-core/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java
File was deleted
opendj-core/src/main/java/com/forgerock/opendj/util/CompletedFutureResult.java
File was deleted
opendj-core/src/main/java/com/forgerock/opendj/util/FutureResultTransformer.java
File was deleted
opendj-core/src/main/java/com/forgerock/opendj/util/RecursiveFutureResult.java
File was deleted
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnection.java
@@ -22,13 +22,11 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2012 ForgeRock AS
 *      Portions copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.CompareRequest;
@@ -42,6 +40,8 @@
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Result;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
/**
 * An abstract connection whose synchronous methods are implemented in terms of
 * asynchronous methods.
@@ -55,133 +55,61 @@
        // No implementation required.
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public Result add(final AddRequest request) throws ErrorResultException {
        final FutureResult<Result> future = addAsync(request, null, null);
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw interrupted(e);
        } finally {
            // Cancel the request if it hasn't completed.
            future.cancel(false);
        }
        return blockingGetOrThrow(addAsync(request));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public BindResult bind(final BindRequest request) throws ErrorResultException {
        final FutureResult<BindResult> future = bindAsync(request, null, null);
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw interrupted(e);
        } finally {
            // Cancel the request if it hasn't completed.
            future.cancel(false);
        }
        return blockingGetOrThrow(bindAsync(request));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public CompareResult compare(final CompareRequest request) throws ErrorResultException {
        final FutureResult<CompareResult> future = compareAsync(request, null, null);
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw interrupted(e);
        } finally {
            // Cancel the request if it hasn't completed.
            future.cancel(false);
        }
        return blockingGetOrThrow(compareAsync(request));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public Result delete(final DeleteRequest request) throws ErrorResultException {
        final FutureResult<Result> future = deleteAsync(request, null, null);
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw interrupted(e);
        } finally {
            // Cancel the request if it hasn't completed.
            future.cancel(false);
        }
        return blockingGetOrThrow(deleteAsync(request));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request,
            final IntermediateResponseHandler handler) throws ErrorResultException {
        final FutureResult<R> future = extendedRequestAsync(request, handler, null);
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw interrupted(e);
        } finally {
            // Cancel the request if it hasn't completed.
            future.cancel(false);
        }
        return blockingGetOrThrow(extendedRequestAsync(request, handler));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public Result modify(final ModifyRequest request) throws ErrorResultException {
        final FutureResult<Result> future = modifyAsync(request, null, null);
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw interrupted(e);
        } finally {
            // Cancel the request if it hasn't completed.
            future.cancel(false);
        }
        return blockingGetOrThrow(modifyAsync(request));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public Result modifyDN(final ModifyDNRequest request) throws ErrorResultException {
        final FutureResult<Result> future = modifyDNAsync(request, null, null);
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw interrupted(e);
        } finally {
            // Cancel the request if it hasn't completed.
            future.cancel(false);
        }
        return blockingGetOrThrow(modifyDNAsync(request));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public Result search(final SearchRequest request, final SearchResultHandler handler)
            throws ErrorResultException {
        final FutureResult<Result> future = searchAsync(request, null, handler);
        return blockingGetOrThrow(searchAsync(request, handler));
    }
    private <T extends Result> T blockingGetOrThrow(FutureResult<T> future) throws ErrorResultException {
        try {
            return future.get();
            return future.getOrThrow();
        } catch (InterruptedException e) {
            throw interrupted(e);
        } finally {
            // Cancel the request if it hasn't completed.
            future.cancel(false);
        }
    }
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java
@@ -22,23 +22,17 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS
 *      Portions copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_NO_SEARCH_RESULT_ENTRIES;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES_NO_COUNT;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
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;
@@ -56,6 +50,12 @@
import org.forgerock.opendj.ldif.ChangeRecordVisitor;
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Function;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static com.forgerock.opendj.ldap.CoreMessages.*;
/**
 * This class provides a skeletal implementation of the {@code Connection}
@@ -63,108 +63,10 @@
 */
public abstract class AbstractConnection implements Connection {
    private static final class SingleEntryFuture implements FutureResult<SearchResultEntry>,
            SearchResultHandler {
        private final ResultHandler<? super SearchResultEntry> handler;
        private final SingleEntryHandler singleEntryHandler = new SingleEntryHandler();
        private volatile FutureResult<Result> future = null;
        private SingleEntryFuture(final ResultHandler<? super SearchResultEntry> handler) {
            this.handler = handler;
        }
        @Override
        public boolean cancel(final boolean mayInterruptIfRunning) {
            return future.cancel(mayInterruptIfRunning);
        }
        @Override
        public SearchResultEntry get() throws ErrorResultException, InterruptedException {
            try {
                future.get();
            } catch (ErrorResultException e) {
                throw singleEntryHandler.filterError(e);
            }
            return get0();
        }
        @Override
        public SearchResultEntry get(final long timeout, final TimeUnit unit) throws ErrorResultException,
                TimeoutException, InterruptedException {
            try {
                future.get(timeout, unit);
            } catch (ErrorResultException e) {
                throw singleEntryHandler.filterError(e);
            }
            return get0();
        }
        @Override
        public int getRequestID() {
            return future.getRequestID();
        }
        @Override
        public boolean handleEntry(final SearchResultEntry entry) {
            return singleEntryHandler.handleEntry(entry);
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
            if (handler != null) {
                ErrorResultException finalError = singleEntryHandler.filterError(error);
                handler.handleErrorResult(finalError);
            }
        }
        @Override
        public boolean handleReference(final SearchResultReference reference) {
            return singleEntryHandler.handleReference(reference);
        }
        @Override
        public void handleResult(final Result result) {
            if (handler != null) {
                try {
                    handler.handleResult(get0());
                } catch (final ErrorResultException e) {
                    handler.handleErrorResult(e);
                }
            }
        }
        @Override
        public boolean isCancelled() {
            return future.isCancelled();
        }
        @Override
        public boolean isDone() {
            return future.isDone();
        }
        private SearchResultEntry get0() throws ErrorResultException {
            ErrorResultException exception = singleEntryHandler.checkForClientSideError();
            if (exception == null) {
                return singleEntryHandler.firstEntry;
            } else {
                throw exception;
            }
        }
        private void setResultFuture(final FutureResult<Result> future) {
            this.future = future;
        }
    }
    private static final class SingleEntryHandler implements SearchResultHandler {
        private volatile SearchResultEntry firstEntry = null;
        private volatile SearchResultReference firstReference = null;
        private volatile int entryCount = 0;
        private volatile SearchResultEntry firstEntry;
        private volatile SearchResultReference firstReference;
        private volatile int entryCount;
        @Override
        public boolean handleEntry(final SearchResultEntry entry) {
@@ -175,14 +77,6 @@
            return true;
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleErrorResult(final ErrorResultException error) {
            // Ignore
        }
        @Override
        public boolean handleReference(final SearchResultReference reference) {
            if (firstReference == null) {
@@ -192,22 +86,17 @@
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleResult(final Result result) {
            // Ignore.
        }
        /**
         * Filter the provided error in order to transform size limit exceeded error to a client side error,
         * or leave it as is for any other error.
         * Filter the provided error in order to transform size limit exceeded
         * error to a client side error, or leave it as is for any other error.
         *
         * @param error to filter
         * @return provided error in most case, or <code>ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED</code>
         * error if provided error is <code>ResultCode.SIZE_LIMIT_EXCEEDED</code>
         * @param error
         *            to filter
         * @return provided error in most case, or
         *         <code>ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED</code>
         *         error if provided error is
         *         <code>ResultCode.SIZE_LIMIT_EXCEEDED</code>
         */
        public ErrorResultException filterError(final ErrorResultException error) {
        private ErrorResultException filterError(final ErrorResultException error) {
            if (error.getResult().getResultCode().equals(ResultCode.SIZE_LIMIT_EXCEEDED)) {
                return newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
                        ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES_NO_COUNT.get().toString());
@@ -217,35 +106,35 @@
        }
        /**
         * Check for any error related to number of search result at client-side level: no result,
         * too many result, search result reference.
         * Check for any error related to number of search result at client-side
         * level: no result, too many result, search result reference. This
         * method should be called only after search operation is finished.
         *
         * This method should be called only after search operation is finished.
         *
         * @return an <code>ErrorResultException</code> if an error is detected, <code>null</code> otherwise
         * @return The single search result entry.
         * @throws ErrorResultException
         *             If an error is detected.
         */
        public ErrorResultException checkForClientSideError() {
            ErrorResultException exception = null;
        private SearchResultEntry getSingleEntry() throws ErrorResultException {
            if (entryCount == 0) {
                // Did not find any entries.
                exception = newErrorResult(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED, ERR_NO_SEARCH_RESULT_ENTRIES
                        .get().toString());
                throw newErrorResult(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED,
                        ERR_NO_SEARCH_RESULT_ENTRIES.get().toString());
            } else if (entryCount > 1) {
                // Got more entries than expected.
                exception = newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
                throw newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
                        ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES.get(entryCount).toString());
            } else if (firstReference != null) {
                // Got an unexpected search result reference.
                exception = newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
                throw newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
                        ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES.get(firstReference.getURIs().iterator().next())
                                .toString());
                        .toString());
            } else {
                return firstEntry;
            }
            return exception;
        }
    }
    // Visitor used for processing synchronous change requests.
    /** Visitor used for processing synchronous change requests. */
    private static final ChangeRecordVisitor<Object, Connection> SYNC_VISITOR =
            new ChangeRecordVisitor<Object, Connection>() {
@@ -268,18 +157,18 @@
                }
                @Override
                public Object visitChangeRecord(final Connection p, final ModifyRequest change) {
                public Object visitChangeRecord(final Connection p, final ModifyDNRequest change) {
                    try {
                        return p.modify(change);
                        return p.modifyDN(change);
                    } catch (final ErrorResultException e) {
                        return e;
                    }
                }
                @Override
                public Object visitChangeRecord(final Connection p, final ModifyDNRequest change) {
                public Object visitChangeRecord(final Connection p, final ModifyRequest change) {
                    try {
                        return p.modifyDN(change);
                        return p.modify(change);
                    } catch (final ErrorResultException e) {
                        return e;
                    }
@@ -293,25 +182,21 @@
        // No implementation required.
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Result add(final Entry entry) throws ErrorResultException {
        return add(Requests.newAddRequest(entry));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Result add(final String... ldifLines) throws ErrorResultException {
        return add(Requests.newAddRequest(ldifLines));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public FutureResult<Result> addAsync(final AddRequest request) {
        return addAsync(request, null);
    }
    @Override
    public Result applyChange(final ChangeRecord request) throws ErrorResultException {
        final Object result = request.accept(SYNC_VISITOR, this);
@@ -322,200 +207,166 @@
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public FutureResult<Result> applyChangeAsync(ChangeRecord request) {
        return applyChangeAsync(request, null);
    }
    @Override
    public FutureResult<Result> applyChangeAsync(final ChangeRecord request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final ChangeRecordVisitor<FutureResult<Result>, Connection> visitor =
                new ChangeRecordVisitor<FutureResult<Result>, Connection>() {
            new ChangeRecordVisitor<FutureResult<Result>, Connection>() {
                    @Override
                    public FutureResult<Result> visitChangeRecord(final Connection p,
                            final AddRequest change) {
                        return p.addAsync(change, intermediateResponseHandler, resultHandler);
                    }
                @Override
                public FutureResult<Result> visitChangeRecord(final Connection p, final AddRequest change) {
                    return p.addAsync(change, intermediateResponseHandler);
                }
                    @Override
                    public FutureResult<Result> visitChangeRecord(final Connection p,
                            final DeleteRequest change) {
                        return p.deleteAsync(change, intermediateResponseHandler, resultHandler);
                    }
                @Override
                public FutureResult<Result> visitChangeRecord(final Connection p, final DeleteRequest change) {
                    return p.deleteAsync(change, intermediateResponseHandler);
                }
                    @Override
                    public FutureResult<Result> visitChangeRecord(final Connection p,
                            final ModifyRequest change) {
                        return p.modifyAsync(change, intermediateResponseHandler, resultHandler);
                    }
                @Override
                public FutureResult<Result> visitChangeRecord(final Connection p, final ModifyDNRequest change) {
                    return p.modifyDNAsync(change, intermediateResponseHandler);
                }
                    @Override
                    public FutureResult<Result> visitChangeRecord(final Connection p,
                            final ModifyDNRequest change) {
                        return p.modifyDNAsync(change, intermediateResponseHandler, resultHandler);
                    }
                };
                @Override
                public FutureResult<Result> visitChangeRecord(final Connection p, final ModifyRequest change) {
                    return p.modifyAsync(change, intermediateResponseHandler);
                }
            };
        return request.accept(visitor, this);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public BindResult bind(final String name, final char[] password) throws ErrorResultException {
        return bind(Requests.newSimpleBindRequest(name, password));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public FutureResult<BindResult> bindAsync(final BindRequest request) {
        return bindAsync(request, null);
    }
    @Override
    public void close() {
        close(Requests.newUnbindRequest(), null);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public CompareResult compare(final String name, final String attributeDescription,
            final String assertionValue) throws ErrorResultException {
    public CompareResult compare(final String name, final String attributeDescription, final String assertionValue)
            throws ErrorResultException {
        return compare(Requests.newCompareRequest(name, attributeDescription, assertionValue));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public FutureResult<CompareResult> compareAsync(final CompareRequest request) {
        return compareAsync(request, null);
    }
    @Override
    public Result delete(final String name) throws ErrorResultException {
        return delete(Requests.newDeleteRequest(name));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Result deleteSubtree(final String name) throws ErrorResultException {
        return delete(Requests.newDeleteRequest(name).addControl(
                SubtreeDeleteRequestControl.newControl(true)));
    public FutureResult<Result> deleteAsync(final DeleteRequest request) {
        return deleteAsync(request, null);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request)
            throws ErrorResultException {
    public Result deleteSubtree(final String name) throws ErrorResultException {
        return delete(Requests.newDeleteRequest(name).addControl(SubtreeDeleteRequestControl.newControl(true)));
    }
    @Override
    public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request) throws ErrorResultException {
        return extendedRequest(request, null);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public GenericExtendedResult extendedRequest(final String requestName,
            final ByteString requestValue) throws ErrorResultException {
    public GenericExtendedResult extendedRequest(final String requestName, final ByteString requestValue)
            throws ErrorResultException {
        return extendedRequest(Requests.newGenericExtendedRequest(requestName, requestValue));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(final ExtendedRequest<R> request) {
        return extendedRequestAsync(request, null);
    }
    @Override
    public Result modify(final String... ldifLines) throws ErrorResultException {
        return modify(Requests.newModifyRequest(ldifLines));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public FutureResult<Result> modifyAsync(final ModifyRequest request) {
        return modifyAsync(request, null);
    }
    @Override
    public Result modifyDN(final String name, final String newRDN) throws ErrorResultException {
        return modifyDN(Requests.newModifyDNRequest(name, newRDN));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request) {
        return modifyDNAsync(request, null);
    }
    @Override
    public SearchResultEntry readEntry(final DN baseObject, final String... attributeDescriptions)
            throws ErrorResultException {
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest(baseObject, SearchScope.BASE_OBJECT, Filter
                        .objectClassPresent(), attributeDescriptions);
            Requests.newSingleEntrySearchRequest(baseObject, SearchScope.BASE_OBJECT, Filter.objectClassPresent(),
                attributeDescriptions);
        return searchSingleEntry(request);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public SearchResultEntry readEntry(final String baseObject,
            final String... attributeDescriptions) throws ErrorResultException {
    public SearchResultEntry readEntry(final String baseObject, final String... attributeDescriptions)
            throws ErrorResultException {
        return readEntry(DN.valueOf(baseObject), attributeDescriptions);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public FutureResult<SearchResultEntry> readEntryAsync(final DN name,
            final Collection<String> attributeDescriptions,
            final ResultHandler<? super SearchResultEntry> handler) {
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest(
                        name, SearchScope.BASE_OBJECT,
                        Filter.objectClassPresent());
            final Collection<String> attributeDescriptions) {
        final SearchRequest request = Requests.newSingleEntrySearchRequest(name, SearchScope.BASE_OBJECT,
                Filter.objectClassPresent());
        if (attributeDescriptions != null) {
            request.getAttributes().addAll(attributeDescriptions);
        }
        return searchSingleEntryAsync(request, handler);
        return searchSingleEntryAsync(request);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public ConnectionEntryReader search(final SearchRequest request) {
        return new ConnectionEntryReader(this, request);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Result search(final SearchRequest request,
            final Collection<? super SearchResultEntry> entries) throws ErrorResultException {
    public Result search(final SearchRequest request, final Collection<? super SearchResultEntry> entries)
            throws ErrorResultException {
        return search(request, entries, null);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Result search(final SearchRequest request,
            final Collection<? super SearchResultEntry> entries,
            final Collection<? super SearchResultReference> references) throws ErrorResultException {
    public Result search(final SearchRequest request, final Collection<? super SearchResultEntry> entries,
        final Collection<? super SearchResultReference> references) throws ErrorResultException {
        Reject.ifNull(request, entries);
        // FIXME: does this need to be thread safe?
        final SearchResultHandler handler = new SearchResultHandler() {
            @Override
            public boolean handleEntry(final SearchResultEntry entry) {
                entries.add(entry);
                return true;
            }
            /**
             * {@inheritDoc}
             */
            @Override
            public void handleErrorResult(final ErrorResultException error) {
                // Ignore.
            }
            @Override
            public boolean handleReference(final SearchResultReference reference) {
                if (references != null) {
@@ -523,72 +374,56 @@
                }
                return true;
            }
            /**
             * {@inheritDoc}
             */
            @Override
            public void handleResult(final Result result) {
                // Ignore.
            }
        };
        return search(request, handler);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public ConnectionEntryReader search(final String baseObject, final SearchScope scope,
            final String filter, final String... attributeDescriptions) {
        final SearchRequest request =
                Requests.newSearchRequest(baseObject, scope, filter, attributeDescriptions);
        return search(request);
    public ConnectionEntryReader search(final String baseObject, final SearchScope scope, final String filter,
        final String... attributeDescriptions) {
        return search(newSearchRequest(baseObject, scope, filter, attributeDescriptions));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public SearchResultEntry searchSingleEntry(final SearchRequest request)
            throws ErrorResultException {
    public FutureResult<Result> searchAsync(final SearchRequest request, final SearchResultHandler resultHandler) {
        return searchAsync(request, null, resultHandler);
    }
    @Override
    public SearchResultEntry searchSingleEntry(final SearchRequest request) throws ErrorResultException {
        final SingleEntryHandler handler = new SingleEntryHandler();
        try {
            search(enforceSingleEntrySearchRequest(request), handler);
        } catch (ErrorResultException e) {
            return handler.getSingleEntry();
        } catch (final ErrorResultException e) {
            throw handler.filterError(e);
        }
        ErrorResultException error = handler.checkForClientSideError();
        if (error == null) {
            return handler.firstEntry;
        } else {
            throw error;
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public SearchResultEntry searchSingleEntry(final String baseObject, final SearchScope scope,
            final String filter, final String... attributeDescriptions) throws ErrorResultException {
    public SearchResultEntry searchSingleEntry(final String baseObject, final SearchScope scope, final String filter,
        final String... attributeDescriptions) throws ErrorResultException {
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest(baseObject, scope, filter, attributeDescriptions);
            Requests.newSingleEntrySearchRequest(baseObject, scope, filter, attributeDescriptions);
        return searchSingleEntry(request);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public FutureResult<SearchResultEntry> searchSingleEntryAsync(final SearchRequest request,
            final ResultHandler<? super SearchResultEntry> handler) {
        final SingleEntryFuture innerFuture = new SingleEntryFuture(handler);
        final FutureResult<Result> future =
                searchAsync(enforceSingleEntrySearchRequest(request), null, innerFuture);
        innerFuture.setResultFuture(future);
        return innerFuture;
    public FutureResult<SearchResultEntry> searchSingleEntryAsync(final SearchRequest request) {
        final SingleEntryHandler handler = new SingleEntryHandler();
        return FutureResultWrapper.asFutureResult(searchAsync(enforceSingleEntrySearchRequest(request), handler).then(
            new Function<Result, SearchResultEntry, ErrorResultException>() {
                @Override
                public SearchResultEntry apply(final Result value) throws ErrorResultException {
                    return handler.getSingleEntry();
                }
            }, new Function<ErrorResultException, SearchResultEntry, ErrorResultException>() {
                @Override
                public SearchResultEntry apply(final ErrorResultException error) throws ErrorResultException {
                    throw handler.filterError(error);
                }
            }));
    }
    /**
@@ -611,6 +446,7 @@
     * <p>
     * Sub-classes should provide an implementation which returns an appropriate
     * description of the connection which may be used for debugging purposes.
     * </p>
     */
    @Override
    public abstract String toString();
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractConnectionWrapper.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS
 *      Portions copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
@@ -122,10 +122,19 @@
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> addAsync(final AddRequest request) {
        return connection.addAsync(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> addAsync(final AddRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
        return connection.addAsync(request, intermediateResponseHandler, resultHandler);
            final IntermediateResponseHandler intermediateResponseHandler) {
        return connection.addAsync(request, intermediateResponseHandler);
    }
    /**
@@ -154,10 +163,19 @@
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> applyChangeAsync(final ChangeRecord request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
        return connection.applyChangeAsync(request, intermediateResponseHandler, resultHandler);
    public FutureResult<Result> applyChangeAsync(ChangeRecord request) {
        return connection.applyChangeAsync(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> applyChangeAsync(ChangeRecord request,
            IntermediateResponseHandler intermediateResponseHandler) {
        return connection.applyChangeAsync(request, intermediateResponseHandler);
    }
    /**
@@ -186,10 +204,19 @@
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<BindResult> bindAsync(final BindRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super BindResult> resultHandler) {
        return connection.bindAsync(request, intermediateResponseHandler, resultHandler);
    public FutureResult<BindResult> bindAsync(final BindRequest request) {
        return connection.bindAsync(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<BindResult> bindAsync(BindRequest request,
            IntermediateResponseHandler intermediateResponseHandler) {
        return connection.bindAsync(request, intermediateResponseHandler);
    }
    /**
@@ -228,8 +255,8 @@
     * The default implementation is to delegate.
     */
    @Override
    public CompareResult compare(final String name, final String attributeDescription,
            final String assertionValue) throws ErrorResultException {
    public CompareResult compare(final String name, final String attributeDescription, final String assertionValue)
            throws ErrorResultException {
        return connection.compare(name, attributeDescription, assertionValue);
    }
@@ -239,10 +266,19 @@
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<CompareResult> compareAsync(final CompareRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super CompareResult> resultHandler) {
        return connection.compareAsync(request, intermediateResponseHandler, resultHandler);
    public FutureResult<CompareResult> compareAsync(final CompareRequest request) {
        return connection.compareAsync(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<CompareResult> compareAsync(CompareRequest request,
            IntermediateResponseHandler intermediateResponseHandler) {
        return connection.compareAsync(request, intermediateResponseHandler);
    }
    /**
@@ -271,10 +307,19 @@
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> deleteAsync(final DeleteRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
        return connection.deleteAsync(request, intermediateResponseHandler, resultHandler);
    public FutureResult<Result> deleteAsync(final DeleteRequest request) {
        return connection.deleteAsync(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> deleteAsync(DeleteRequest request,
            IntermediateResponseHandler intermediateResponseHandler) {
        return connection.deleteAsync(request, intermediateResponseHandler);
    }
    /**
@@ -293,8 +338,7 @@
     * The default implementation is to delegate.
     */
    @Override
    public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request)
            throws ErrorResultException {
    public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request) throws ErrorResultException {
        return connection.extendedRequest(request);
    }
@@ -315,8 +359,8 @@
     * The default implementation is to delegate.
     */
    @Override
    public GenericExtendedResult extendedRequest(final String requestName,
            final ByteString requestValue) throws ErrorResultException {
    public GenericExtendedResult extendedRequest(final String requestName, final ByteString requestValue)
            throws ErrorResultException {
        return connection.extendedRequest(requestName, requestValue);
    }
@@ -326,11 +370,19 @@
     * The default implementation is to delegate.
     */
    @Override
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
            final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super R> resultHandler) {
        return connection.extendedRequestAsync(request, intermediateResponseHandler, resultHandler);
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(final ExtendedRequest<R> request) {
        return connection.extendedRequestAsync(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(ExtendedRequest<R> request,
            IntermediateResponseHandler intermediateResponseHandler) {
        return connection.extendedRequestAsync(request, intermediateResponseHandler);
    }
    /**
@@ -379,10 +431,19 @@
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> modifyAsync(final ModifyRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
        return connection.modifyAsync(request, intermediateResponseHandler, resultHandler);
    public FutureResult<Result> modifyAsync(final ModifyRequest request) {
        return connection.modifyAsync(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> modifyAsync(ModifyRequest request,
            IntermediateResponseHandler intermediateResponseHandler) {
        return connection.modifyAsync(request, intermediateResponseHandler);
    }
    /**
@@ -411,10 +472,19 @@
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
        return connection.modifyDNAsync(request, intermediateResponseHandler, resultHandler);
    public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request) {
        return connection.modifyDNAsync(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<Result> modifyDNAsync(ModifyDNRequest request,
            IntermediateResponseHandler intermediateResponseHandler) {
        return modifyDNAsync(request, intermediateResponseHandler);
    }
    /**
@@ -446,9 +516,8 @@
     */
    @Override
    public FutureResult<SearchResultEntry> readEntryAsync(final DN name,
            final Collection<String> attributeDescriptions,
            final ResultHandler<? super SearchResultEntry> handler) {
        return connection.readEntryAsync(name, attributeDescriptions, handler);
            final Collection<String> attributeDescriptions) {
        return connection.readEntryAsync(name, attributeDescriptions);
    }
    /**
@@ -523,9 +592,8 @@
     */
    @Override
    public FutureResult<Result> searchAsync(final SearchRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final SearchResultHandler resultHandler) {
        return connection.searchAsync(request, intermediateResponseHandler, resultHandler);
        return connection.searchAsync(request, resultHandler);
    }
    /**
@@ -534,8 +602,18 @@
     * The default implementation is to delegate.
     */
    @Override
    public SearchResultEntry searchSingleEntry(final SearchRequest request)
            throws ErrorResultException {
    public FutureResult<Result> searchAsync(SearchRequest request,
            IntermediateResponseHandler intermediateResponseHandler, SearchResultHandler entryHandler) {
        return connection.searchAsync(request, intermediateResponseHandler, entryHandler);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The default implementation is to delegate.
     */
    @Override
    public SearchResultEntry searchSingleEntry(final SearchRequest request) throws ErrorResultException {
        return connection.searchSingleEntry(request);
    }
@@ -545,8 +623,8 @@
     * The default implementation is to delegate.
     */
    @Override
    public SearchResultEntry searchSingleEntry(final String baseObject, final SearchScope scope,
            final String filter, final String... attributeDescriptions) throws ErrorResultException {
    public SearchResultEntry searchSingleEntry(final String baseObject, final SearchScope scope, final String filter,
            final String... attributeDescriptions) throws ErrorResultException {
        return connection.searchSingleEntry(baseObject, scope, filter, attributeDescriptions);
    }
@@ -556,9 +634,8 @@
     * The default implementation is to delegate.
     */
    @Override
    public FutureResult<SearchResultEntry> searchSingleEntryAsync(final SearchRequest request,
            final ResultHandler<? super SearchResultEntry> handler) {
        return connection.searchSingleEntryAsync(request, handler);
    public FutureResult<SearchResultEntry> searchSingleEntryAsync(final SearchRequest request) {
        return connection.searchSingleEntryAsync(request);
    }
    /**
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractLoadBalancingAlgorithm.java
@@ -26,9 +26,6 @@
 */
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.DEFAULT_SCHEDULER;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -40,10 +37,16 @@
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.Promise;
import com.forgerock.opendj.util.AsynchronousFutureResult;
import com.forgerock.opendj.util.ReferenceCountedObject;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.util.promise.Promises.*;
import static com.forgerock.opendj.util.StaticUtils.*;
/**
 * An abstract load balancing algorithm providing monitoring and failover
 * capabilities.
@@ -58,7 +61,7 @@
        private final ConnectionFactory factory;
        private final AtomicBoolean isOperational = new AtomicBoolean(true);
        private volatile FutureResult<?> pendingConnectFuture = null;
        private volatile Promise<?, ErrorResultException> pendingConnectPromise;
        private final int index;
        private MonitoredConnectionFactory(final ConnectionFactory factory, final int index) {
@@ -90,43 +93,35 @@
        }
        @Override
        public FutureResult<Connection> getConnectionAsync(
                final ResultHandler<? super Connection> resultHandler) {
            final AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> future =
                    new AsynchronousFutureResult<Connection, ResultHandler<? super Connection>>(
                            resultHandler);
            final ResultHandler<Connection> failoverHandler = new ResultHandler<Connection>() {
                @Override
                public void handleErrorResult(final ErrorResultException error) {
                    // Attempt failed - try next factory.
                    notifyOffline(error);
                    final int nextIndex = (index + 1) % monitoredFactories.size();
                    try {
                        final MonitoredConnectionFactory nextFactory =
                                getMonitoredConnectionFactory(nextIndex);
                        nextFactory.getConnectionAsync(future);
                    } catch (final ErrorResultException e) {
                        future.handleErrorResult(e);
        public Promise<Connection, ErrorResultException> getConnectionAsync() {
            return factory.getConnectionAsync().thenAsync(
                new AsyncFunction<Connection, Connection, ErrorResultException>() {
                    @Override
                    public Promise<Connection, ErrorResultException> apply(Connection value)
                            throws ErrorResultException {
                        notifyOnline();
                        return newSuccessfulPromise(value);
                    }
                }
                @Override
                public void handleResult(final Connection result) {
                    notifyOnline();
                    future.handleResult(result);
                }
            };
            factory.getConnectionAsync(failoverHandler);
            return future;
                },
                new AsyncFunction<ErrorResultException, Connection, ErrorResultException>() {
                    @Override
                    public Promise<Connection, ErrorResultException> apply(ErrorResultException error)
                            throws ErrorResultException {
                        // Attempt failed - try next factory.
                        notifyOffline(error);
                        final int nextIndex = (index + 1) % monitoredFactories.size();
                        return getMonitoredConnectionFactory(nextIndex).getConnectionAsync();
                    }
                });
        }
        /**
         * Handle monitoring connection request failure.
         */
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            notifyOffline(error);
        }
@@ -150,10 +145,9 @@
         * pending monitoring request.
         */
        private synchronized void checkIfAvailable() {
            if (!isOperational.get()
                    && (pendingConnectFuture == null || pendingConnectFuture.isDone())) {
            if (!isOperational.get() && (pendingConnectPromise == null || pendingConnectPromise.isDone())) {
                logger.debug(LocalizableMessage.raw("Attempting reconnect to offline factory '%s'", this));
                pendingConnectFuture = factory.getConnectionAsync(this);
                pendingConnectPromise = factory.getConnectionAsync().onSuccess(this).onFailure(this);
            }
        }
@@ -279,7 +273,7 @@
     * Guarded by stateLock.
     */
    private ScheduledFuture<?> monitoringFuture;
    private AtomicBoolean isClosed = new AtomicBoolean();
    private final AtomicBoolean isClosed = new AtomicBoolean();
    AbstractLoadBalancingAlgorithm(final Collection<? extends ConnectionFactory> factories,
            final LoadBalancerEventListener listener, final long interval, final TimeUnit unit,
opendj-core/src/main/java/org/forgerock/opendj/ldap/AbstractSynchronousConnection.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2012 ForgeRock AS
 *      Copyright 2012-2014 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
@@ -40,7 +40,7 @@
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Result;
import com.forgerock.opendj.util.CompletedFutureResult;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
/**
 * An abstract connection whose asynchronous methods are implemented in terms of
@@ -74,113 +74,95 @@
     */
    @Override
    public FutureResult<Void> abandonAsync(final AbandonRequest request) {
        throw new UnsupportedOperationException(
                "Abandon requests are not supported for synchronous connections");
        throw new UnsupportedOperationException("Abandon requests are not supported for synchronous connections");
    }
    @Override
    public FutureResult<Result> addAsync(final AddRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        try {
            return onSuccess(add(request), resultHandler);
            return onSuccess(add(request));
        } catch (final ErrorResultException e) {
            return onFailure(e, resultHandler);
            return onFailure(e);
        }
    }
    @Override
    public FutureResult<BindResult> bindAsync(final BindRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super BindResult> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        try {
            return onSuccess(bind(request), resultHandler);
            return onSuccess(bind(request));
        } catch (final ErrorResultException e) {
            return onFailure(e, resultHandler);
            return onFailure(e);
        }
    }
    @Override
    public FutureResult<CompareResult> compareAsync(final CompareRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super CompareResult> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        try {
            return onSuccess(compare(request), resultHandler);
            return onSuccess(compare(request));
        } catch (final ErrorResultException e) {
            return onFailure(e, resultHandler);
            return onFailure(e);
        }
    }
    @Override
    public FutureResult<Result> deleteAsync(final DeleteRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        try {
            return onSuccess(delete(request), resultHandler);
            return onSuccess(delete(request));
        } catch (final ErrorResultException e) {
            return onFailure(e, resultHandler);
            return onFailure(e);
        }
    }
    @Override
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
            final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super R> resultHandler) {
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler) {
        try {
            return onSuccess(extendedRequest(request, intermediateResponseHandler), resultHandler);
            return onSuccess(extendedRequest(request, intermediateResponseHandler));
        } catch (final ErrorResultException e) {
            return onFailure(e, resultHandler);
            return onFailure(e);
        }
    }
    @Override
    public FutureResult<Result> modifyAsync(final ModifyRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        try {
            return onSuccess(modify(request), resultHandler);
            return onSuccess(modify(request));
        } catch (final ErrorResultException e) {
            return onFailure(e, resultHandler);
            return onFailure(e);
        }
    }
    @Override
    public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        try {
            return onSuccess(modifyDN(request), resultHandler);
            return onSuccess(modifyDN(request));
        } catch (final ErrorResultException e) {
            return onFailure(e, resultHandler);
            return onFailure(e);
        }
    }
    @Override
    public FutureResult<Result> searchAsync(final SearchRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final SearchResultHandler resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
        try {
            return onSuccess(search(request, resultHandler), resultHandler);
            return onSuccess(search(request, entryHandler));
        } catch (final ErrorResultException e) {
            return onFailure(e, resultHandler);
            return onFailure(e);
        }
    }
    private <R extends Result> FutureResult<R> onFailure(final ErrorResultException e,
            final ResultHandler<? super R> resultHandler) {
        if (resultHandler != null) {
            resultHandler.handleErrorResult(e);
        }
        return new CompletedFutureResult<R>(e);
    private <R extends Result> FutureResult<R> onFailure(final ErrorResultException e) {
        return newFailedFutureResult(e);
    }
    private <R extends Result> FutureResult<R> onSuccess(final R result,
            final ResultHandler<? super R> resultHandler) {
        if (resultHandler != null) {
            resultHandler.handleResult(result);
        }
        return new CompletedFutureResult<R>(result);
    private <R extends Result> FutureResult<R> onSuccess(final R result) {
        return newSuccessfulFutureResult(result);
    }
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java
@@ -22,16 +22,20 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.Function;
import org.forgerock.util.promise.Promise;
import com.forgerock.opendj.util.FutureResultTransformer;
import com.forgerock.opendj.util.RecursiveFutureResult;
import static org.forgerock.util.Utils.*;
/**
 * An authenticated connection factory can be used to create pre-authenticated
@@ -57,28 +61,27 @@
            super(connection);
        }
        /*
        /**
         * Bind operations are not supported by pre-authenticated connections.
         * These methods will always throw {@code UnsupportedOperationException}.
         */
        public FutureResult<BindResult> bindAsync(final BindRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super BindResult> resultHandler) {
            throw new UnsupportedOperationException();
        }
        @Override
        public BindResult bind(BindRequest request) throws ErrorResultException {
            throw new UnsupportedOperationException();
        }
        @Override
        public BindResult bind(String name, char[] password) throws ErrorResultException {
            throw new UnsupportedOperationException();
        }
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("AuthenticatedConnection(");
@@ -89,52 +92,6 @@
    }
    private static final class FutureResultImpl {
        private final FutureResultTransformer<BindResult, Connection> futureBindResult;
        private final RecursiveFutureResult<Connection, BindResult> futureConnectionResult;
        private final BindRequest bindRequest;
        private Connection connection;
        private FutureResultImpl(final BindRequest request,
                final ResultHandler<? super Connection> handler) {
            this.bindRequest = request;
            this.futureBindResult = new FutureResultTransformer<BindResult, Connection>(handler) {
                @Override
                protected ErrorResultException transformErrorResult(
                        final ErrorResultException errorResult) {
                    // Ensure that the connection is closed.
                    if (connection != null) {
                        connection.close();
                        connection = null;
                    }
                    return errorResult;
                }
                @Override
                protected Connection transformResult(final BindResult result)
                        throws ErrorResultException {
                    return new AuthenticatedConnection(connection);
                }
            };
            this.futureConnectionResult =
                    new RecursiveFutureResult<Connection, BindResult>(futureBindResult) {
                        @Override
                        protected FutureResult<? extends BindResult> chainResult(
                                final Connection innerResult,
                                final ResultHandler<? super BindResult> handler)
                                throws ErrorResultException {
                            connection = innerResult;
                            return connection.bindAsync(bindRequest, null, handler);
                        }
                    };
            futureBindResult.setFutureResult(futureConnectionResult);
        }
    }
    private final BindRequest request;
    private final ConnectionFactory parentFactory;
@@ -162,6 +119,7 @@
        parentFactory.close();
    }
    @Override
    public Connection getConnection() throws ErrorResultException {
        final Connection connection = parentFactory.getConnection();
        boolean bindSucceeded = false;
@@ -181,25 +139,44 @@
        return new AuthenticatedConnection(connection);
    }
    @Override
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> handler) {
        final FutureResultImpl future = new FutureResultImpl(request, handler);
        future.futureConnectionResult.setFutureResult(parentFactory
                .getConnectionAsync(future.futureConnectionResult));
        return future.futureBindResult;
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
        return parentFactory.getConnectionAsync()
            .thenAsync(
                    new AsyncFunction<Connection, BindResult, ErrorResultException>() {
                        @Override
                        public Promise<BindResult, ErrorResultException> apply(final Connection connection)
                                throws ErrorResultException {
                            connectionHolder.set(connection);
                            return connection.bindAsync(request);
                        }
                    }
            ).then(
                    new Function<BindResult, Connection, ErrorResultException>() {
                        @Override
                        public Connection apply(BindResult result) throws ErrorResultException {
                            return new AuthenticatedConnection(connectionHolder.get());
                        }
                    },
                    new Function<ErrorResultException, Connection, ErrorResultException>() {
                        @Override
                        public Connection apply(ErrorResultException error) throws ErrorResultException {
                            closeSilently(connectionHolder.get());
                            throw error;
                        }
                    }
            );
    }
    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("AuthenticatedConnectionFactory(");
        builder.append(String.valueOf(parentFactory));
        builder.append(parentFactory);
        builder.append(", ");
        builder.append(request);
        builder.append(')');
        return builder.toString();
    }
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/CachedConnectionPool.java
@@ -26,13 +26,6 @@
 */
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.DEBUG_ENABLED;
import static com.forgerock.opendj.util.StaticUtils.DEFAULT_SCHEDULER;
import static com.forgerock.opendj.util.StaticUtils.getStackTraceIfDebugEnabled;
import static com.forgerock.opendj.util.StaticUtils.logIfDebugEnabled;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_CONNECTION_POOL_CLOSING;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -66,12 +59,19 @@
import org.forgerock.opendj.ldif.ChangeRecord;
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.SuccessHandler;
import com.forgerock.opendj.util.AsynchronousFutureResult;
import com.forgerock.opendj.util.CompletedFutureResult;
import com.forgerock.opendj.util.ReferenceCountedObject;
import com.forgerock.opendj.util.TimeSource;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.util.promise.Promises.*;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import static com.forgerock.opendj.util.StaticUtils.*;
/**
 * A connection pool implementation which maintains a cache of pooled
 * connections with a configurable core pool size, maximum size, and expiration
@@ -80,13 +80,27 @@
final class CachedConnectionPool implements ConnectionPool {
    /**
     * This result handler is invoked when an attempt to add a new connection to
     * the pool completes.
     * This success handler is invoked when an attempt to add a new connection
     * to the pool completes.
     */
    private final class ConnectionResultHandler implements ResultHandler<Connection> {
    private final class ConnectionSuccessHandler implements SuccessHandler<Connection> {
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleResult(final Connection connection) {
            logger.debug(LocalizableMessage.raw(
                    "Connection attempt succeeded:  availableConnections=%d, maxPoolSize=%d",
                     currentPoolSize(), maxPoolSize));
            pendingConnectionAttempts.decrementAndGet();
            publishConnection(connection);
        }
    }
    /**
     * This failure handler is invoked when an attempt to add a new connection
     * to the pool ended in error.
     */
    private final class ConnectionFailureHandler implements FailureHandler<ErrorResultException> {
        @Override
        public void handleError(final ErrorResultException error) {
            // Connection attempt failed, so decrease the pool size.
            pendingConnectionAttempts.decrementAndGet();
            availableConnections.release();
@@ -114,18 +128,9 @@
                }
            }
            for (QueueElement waitingFuture : waitingFutures) {
                waitingFuture.getWaitingFuture().handleErrorResult(error);
                waitingFuture.getWaitingFuture().handleError(error);
            }
        }
        @Override
        public void handleResult(final Connection connection) {
            logger.debug(LocalizableMessage.raw(
                    "Connection attempt succeeded:  availableConnections=%d, maxPoolSize=%d",
                    currentPoolSize(), maxPoolSize));
            pendingConnectionAttempts.decrementAndGet();
            publishConnection(connection);
        }
    }
    /**
@@ -167,10 +172,14 @@
        }
        @Override
        public FutureResult<Result> addAsync(AddRequest request) {
            return addAsync(request, null);
        }
        @Override
        public FutureResult<Result> addAsync(final AddRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            return checkState().addAsync(request, intermediateResponseHandler, resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
            return checkState().addAsync(request, intermediateResponseHandler);
        }
        @Override
@@ -211,11 +220,14 @@
        }
        @Override
        public FutureResult<Result> applyChangeAsync(final ChangeRecord request) {
            return checkState().applyChangeAsync(request, null);
        }
        @Override
        public FutureResult<Result> applyChangeAsync(final ChangeRecord request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            return checkState().applyChangeAsync(request, intermediateResponseHandler,
                    resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
            return checkState().applyChangeAsync(request, intermediateResponseHandler);
        }
        @Override
@@ -230,10 +242,14 @@
        }
        @Override
        public FutureResult<BindResult> bindAsync(BindRequest request) {
            return bindAsync(request, null);
        }
        @Override
        public FutureResult<BindResult> bindAsync(final BindRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super BindResult> resultHandler) {
            return checkState().bindAsync(request, intermediateResponseHandler, resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
            return checkState().bindAsync(request, intermediateResponseHandler);
        }
        @Override
@@ -268,7 +284,7 @@
                 */
                connection.close();
                pendingConnectionAttempts.incrementAndGet();
                factory.getConnectionAsync(connectionResultHandler);
                factory.getConnectionAsync().onSuccess(connectionSuccessHandler).onFailure(connectionFailureHandler);
                logger.debug(LocalizableMessage.raw(
                        "Connection no longer valid: availableConnections=%d, maxPoolSize=%d",
@@ -300,10 +316,14 @@
        }
        @Override
        public FutureResult<CompareResult> compareAsync(CompareRequest request) {
            return compareAsync(request, null);
        }
        @Override
        public FutureResult<CompareResult> compareAsync(final CompareRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super CompareResult> resultHandler) {
            return checkState().compareAsync(request, intermediateResponseHandler, resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
            return checkState().compareAsync(request, intermediateResponseHandler);
        }
        @Override
@@ -317,10 +337,14 @@
        }
        @Override
        public FutureResult<Result> deleteAsync(DeleteRequest request) {
            return deleteAsync(request, null);
        }
        @Override
        public FutureResult<Result> deleteAsync(final DeleteRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            return checkState().deleteAsync(request, intermediateResponseHandler, resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
            return checkState().deleteAsync(request, intermediateResponseHandler);
        }
        @Override
@@ -347,12 +371,14 @@
        }
        @Override
        public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
                final ExtendedRequest<R> request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super R> resultHandler) {
            return checkState().extendedRequestAsync(request, intermediateResponseHandler,
                    resultHandler);
        public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(ExtendedRequest<R> request) {
            return extendedRequestAsync(request, null);
        }
        @Override
        public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(final ExtendedRequest<R> request,
                final IntermediateResponseHandler intermediateResponseHandler) {
            return checkState().extendedRequestAsync(request, intermediateResponseHandler);
        }
        @Override
@@ -367,8 +393,7 @@
        }
        @Override
        public void handleConnectionError(final boolean isDisconnectNotification,
                final ErrorResultException error) {
        public void handleConnectionError(final boolean isDisconnectNotification, final ErrorResultException error) {
            final List<ConnectionEventListener> tmpListeners;
            synchronized (stateLock) {
                tmpListeners = listeners;
@@ -416,10 +441,14 @@
        }
        @Override
        public FutureResult<Result> modifyAsync(ModifyRequest request) {
            return modifyAsync(request, null);
        }
        @Override
        public FutureResult<Result> modifyAsync(final ModifyRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            return checkState().modifyAsync(request, intermediateResponseHandler, resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
            return checkState().modifyAsync(request, intermediateResponseHandler);
        }
        @Override
@@ -433,10 +462,14 @@
        }
        @Override
        public FutureResult<Result> modifyDNAsync(ModifyDNRequest request) {
            return modifyDNAsync(request, null);
        }
        @Override
        public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            return checkState().modifyDNAsync(request, intermediateResponseHandler, resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
            return checkState().modifyDNAsync(request, intermediateResponseHandler);
        }
        @Override
@@ -453,9 +486,8 @@
        @Override
        public FutureResult<SearchResultEntry> readEntryAsync(final DN name,
                final Collection<String> attributeDescriptions,
                final ResultHandler<? super SearchResultEntry> handler) {
            return checkState().readEntryAsync(name, attributeDescriptions, handler);
                final Collection<String> attributeDescriptions) {
            return checkState().readEntryAsync(name, attributeDescriptions);
        }
        @Override
@@ -474,16 +506,14 @@
        }
        @Override
        public Result search(final SearchRequest request,
                final Collection<? super SearchResultEntry> entries) throws ErrorResultException {
        public Result search(final SearchRequest request, final Collection<? super SearchResultEntry> entries)
                throws ErrorResultException {
            return checkState().search(request, entries);
        }
        @Override
        public Result search(final SearchRequest request,
                final Collection<? super SearchResultEntry> entries,
                final Collection<? super SearchResultReference> references)
                throws ErrorResultException {
        public Result search(final SearchRequest request, final Collection<? super SearchResultEntry> entries,
                final Collection<? super SearchResultReference> references) throws ErrorResultException {
            return checkState().search(request, entries, references);
        }
@@ -494,35 +524,36 @@
        }
        @Override
        public ConnectionEntryReader search(final String baseObject, final SearchScope scope,
                final String filter, final String... attributeDescriptions) {
        public ConnectionEntryReader search(final String baseObject, final SearchScope scope, final String filter,
                final String... attributeDescriptions) {
            return checkState().search(baseObject, scope, filter, attributeDescriptions);
        }
        @Override
        public FutureResult<Result> searchAsync(final SearchRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final SearchResultHandler resultHandler) {
            return checkState().searchAsync(request, intermediateResponseHandler, resultHandler);
        public FutureResult<Result> searchAsync(SearchRequest request, SearchResultHandler resultHandler) {
            return searchAsync(request, null, resultHandler);
        }
        @Override
        public SearchResultEntry searchSingleEntry(final SearchRequest request)
                throws ErrorResultException {
        public FutureResult<Result> searchAsync(final SearchRequest request,
                final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
            return checkState().searchAsync(request, intermediateResponseHandler, entryHandler);
        }
        @Override
        public SearchResultEntry searchSingleEntry(final SearchRequest request) throws ErrorResultException {
            return checkState().searchSingleEntry(request);
        }
        @Override
        public SearchResultEntry searchSingleEntry(final String baseObject,
                final SearchScope scope, final String filter, final String... attributeDescriptions)
                throws ErrorResultException {
        public SearchResultEntry searchSingleEntry(final String baseObject, final SearchScope scope,
                final String filter, final String... attributeDescriptions) throws ErrorResultException {
            return checkState().searchSingleEntry(baseObject, scope, filter, attributeDescriptions);
        }
        @Override
        public FutureResult<SearchResultEntry> searchSingleEntryAsync(final SearchRequest request,
                final ResultHandler<? super SearchResultEntry> handler) {
            return checkState().searchSingleEntryAsync(request, handler);
        public FutureResult<SearchResultEntry> searchSingleEntryAsync(final SearchRequest request) {
            return checkState().searchSingleEntryAsync(request);
        }
        @Override
@@ -623,11 +654,9 @@
            this.stack = null;
        }
        QueueElement(final ResultHandler<? super Connection> handler, final long timestampMillis,
                final StackTraceElement[] stack) {
            this.value =
                    new AsynchronousFutureResult<Connection, ResultHandler<? super Connection>>(
                            handler);
        QueueElement(final long timestampMillis,
            final StackTraceElement[] stack) {
            this.value = new FutureResultImpl<Connection>();
            this.timestampMillis = timestampMillis;
            this.stack = stack;
        }
@@ -650,8 +679,8 @@
        }
        @SuppressWarnings("unchecked")
        AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> getWaitingFuture() {
            return (AsynchronousFutureResult<Connection, ResultHandler<? super Connection>>) value;
        FutureResultImpl<Connection> getWaitingFuture() {
            return (FutureResultImpl<Connection>) value;
        }
        boolean hasTimedOut(final long timeLimitMillis) {
@@ -659,7 +688,7 @@
        }
        boolean isWaitingFuture() {
            return value instanceof AsynchronousFutureResult;
            return value instanceof FutureResultImpl;
        }
    }
@@ -672,7 +701,8 @@
    TimeSource timeSource = TimeSource.DEFAULT;
    private final Semaphore availableConnections;
    private final ResultHandler<Connection> connectionResultHandler = new ConnectionResultHandler();
    private final SuccessHandler<Connection> connectionSuccessHandler = new ConnectionSuccessHandler();
    private final FailureHandler<ErrorResultException> connectionFailureHandler = new ConnectionFailureHandler();
    private final int corePoolSize;
    private final ConnectionFactory factory;
    private boolean isClosed = false;
@@ -760,15 +790,14 @@
    @Override
    public Connection getConnection() throws ErrorResultException {
        try {
            return getConnectionAsync(null).get();
            return getConnectionAsync().getOrThrow();
        } catch (final InterruptedException e) {
            throw newErrorResult(ResultCode.CLIENT_SIDE_USER_CANCELLED, e);
        }
    }
    @Override
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> handler) {
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        // Loop while iterating through stale connections (see OPENDJ-590).
        for (;;) {
            final QueueElement holder;
@@ -778,9 +807,7 @@
                } else if (hasWaitingConnections()) {
                    holder = queue.removeFirst();
                } else {
                    holder =
                            new QueueElement(handler, timeSource.currentTimeMillis(),
                                    getStackTraceIfDebugEnabled());
                    holder = new QueueElement(timeSource.currentTimeMillis(), getStackTraceIfDebugEnabled());
                    queue.add(holder);
                }
            }
@@ -790,7 +817,8 @@
                final FutureResult<Connection> future = holder.getWaitingFuture();
                if (!future.isDone() && availableConnections.tryAcquire()) {
                    pendingConnectionAttempts.incrementAndGet();
                    factory.getConnectionAsync(connectionResultHandler);
                    factory.getConnectionAsync().onSuccess(connectionSuccessHandler)
                                                .onFailure(connectionFailureHandler);
                }
                return future;
            }
@@ -798,19 +826,14 @@
            // There was a completed connection attempt.
            final Connection connection = holder.getWaitingConnection();
            if (connection.isValid()) {
                final PooledConnection pooledConnection =
                        newPooledConnection(connection, getStackTraceIfDebugEnabled());
                if (handler != null) {
                    handler.handleResult(pooledConnection);
                }
                return new CompletedFutureResult<Connection>(pooledConnection);
                final Connection pooledConnection = newPooledConnection(connection, getStackTraceIfDebugEnabled());
                return newSuccessfulPromise(pooledConnection);
            } else {
                // Close the stale connection and try again.
                connection.close();
                availableConnections.release();
                logger.debug(LocalizableMessage.raw(
                        "Connection no longer valid: availableConnections=%d, poolSize=%d",
                logger.debug(LocalizableMessage.raw("Connection no longer valid: availableConnections=%d, poolSize=%d",
                        currentPoolSize(), maxPoolSize));
            }
        }
@@ -892,7 +915,7 @@
                final ErrorResultException e =
                        ErrorResultException.newErrorResult(ResultCode.CLIENT_SIDE_USER_CANCELLED,
                                ERR_CONNECTION_POOL_CLOSING.get(toString()).toString());
                holder.getWaitingFuture().handleErrorResult(e);
                holder.getWaitingFuture().handleError(e);
                logger.debug(LocalizableMessage.raw(
                        "Connection attempt failed: availableConnections=%d, maxPoolSize=%d",
opendj-core/src/main/java/org/forgerock/opendj/ldap/Connection.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS
 *      Portions copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
@@ -258,13 +258,6 @@
     *
     * @param request
     *            The add request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support add operations.
@@ -274,9 +267,28 @@
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> addAsync(AddRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            ResultHandler<? super Result> resultHandler);
    FutureResult<Result> addAsync(AddRequest request);
    /**
     * Asynchronously adds an entry to the Directory Server using the provided
     * add request.
     *
     * @param request
     *            The add request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support add operations.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> addAsync(AddRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Registers the provided connection event listener so that it will be
@@ -320,13 +332,28 @@
     *
     * @param request
     *            The change request.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support the provided change
     *             request.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> applyChangeAsync(ChangeRecord request);
    /**
     * Asynchronously applies the provided change request to the Directory
     * Server.
     *
     * @param request
     *            The change request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support the provided change
@@ -338,8 +365,7 @@
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> applyChangeAsync(ChangeRecord request,
            IntermediateResponseHandler intermediateResponseHandler,
            ResultHandler<? super Result> resultHandler);
        IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Authenticates to the Directory Server using the provided bind request.
@@ -400,13 +426,6 @@
     *
     * @param request
     *            The bind request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support bind operations.
@@ -416,9 +435,28 @@
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<BindResult> bindAsync(BindRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            ResultHandler<? super BindResult> resultHandler);
    FutureResult<BindResult> bindAsync(BindRequest request);
    /**
     * Asynchronously authenticates to the Directory Server using the provided
     * bind request.
     *
     * @param request
     *            The bind request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support bind operations.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<BindResult> bindAsync(BindRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Releases any resources associated with this connection. For physical
@@ -530,13 +568,27 @@
     *
     * @param request
     *            The compare request.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support compare operations.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<CompareResult> compareAsync(CompareRequest request);
    /**
     * Asynchronously compares an entry in the Directory Server using the
     * provided compare request.
     *
     * @param request
     *            The compare request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support compare operations.
@@ -547,8 +599,7 @@
     *             If {@code request} was {@code null}.
     */
    FutureResult<CompareResult> compareAsync(CompareRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            ResultHandler<? super CompareResult> resultHandler);
        IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Deletes an entry from the Directory Server using the provided delete
@@ -636,13 +687,6 @@
     *
     * @param request
     *            The delete request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support delete operations.
@@ -652,9 +696,28 @@
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> deleteAsync(DeleteRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            ResultHandler<? super Result> resultHandler);
    FutureResult<Result> deleteAsync(DeleteRequest request);
    /**
     * Asynchronously deletes an entry from the Directory Server using the
     * provided delete request.
     *
     * @param request
     *            The delete request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support delete operations.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> deleteAsync(DeleteRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Requests that the Directory Server performs the provided extended
@@ -703,8 +766,8 @@
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    <R extends ExtendedResult> R extendedRequest(ExtendedRequest<R> request,
            IntermediateResponseHandler handler) throws ErrorResultException;
    <R extends ExtendedResult> R extendedRequest(ExtendedRequest<R> request, IntermediateResponseHandler handler)
            throws ErrorResultException;
    /**
     * Requests that the Directory Server performs the provided extended
@@ -746,13 +809,29 @@
     *            The type of result returned by the extended request.
     * @param request
     *            The extended request.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support extended operations.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(ExtendedRequest<R> request);
    /**
     * Asynchronously performs the provided extended request in the Directory
     * Server.
     *
     * @param <R>
     *            The type of result returned by the extended request.
     * @param request
     *            The extended request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support extended operations.
@@ -763,8 +842,7 @@
     *             If {@code request} was {@code null}.
     */
    <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(ExtendedRequest<R> request,
            IntermediateResponseHandler intermediateResponseHandler,
            ResultHandler<? super R> resultHandler);
        IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Indicates whether or not this connection has been explicitly closed by
@@ -845,13 +923,6 @@
     *
     * @param request
     *            The modify request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support modify operations.
@@ -861,9 +932,28 @@
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> modifyAsync(ModifyRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            ResultHandler<? super Result> resultHandler);
    FutureResult<Result> modifyAsync(ModifyRequest request);
    /**
     * Asynchronously modifies an entry in the Directory Server using the
     * provided modify request.
     *
     * @param request
     *            The modify request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support modify operations.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> modifyAsync(ModifyRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Renames an entry in the Directory Server using the provided modify DN
@@ -923,13 +1013,27 @@
     *
     * @param request
     *            The modify DN request.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support modify DN operations.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> modifyDNAsync(ModifyDNRequest request);
    /**
     * Asynchronously renames an entry in the Directory Server using the
     * provided modify DN request.
     *
     * @param request
     *            The modify DN request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support modify DN operations.
@@ -940,8 +1044,7 @@
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> modifyDNAsync(ModifyDNRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            ResultHandler<? super Result> resultHandler);
        IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Reads the named entry from the Directory Server.
@@ -953,8 +1056,8 @@
     * This method is equivalent to the following code:
     *
     * <pre>
     * SearchRequest request =
     *         new SearchRequest(name, SearchScope.BASE_OBJECT, &quot;(objectClass=*)&quot;, attributeDescriptions);
     * SearchRequest request = new SearchRequest(name, SearchScope.BASE_OBJECT,
     * &quot;(objectClass=*)&quot;, attributeDescriptions);
     * connection.searchSingleEntry(request);
     * </pre>
     *
@@ -1037,9 +1140,6 @@
     *            The names of the attributes to be included with the entry,
     *            which may be {@code null} or empty indicating that all user
     *            attributes should be returned.
     * @param handler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support search operations.
@@ -1049,9 +1149,7 @@
     * @throws NullPointerException
     *             If the {@code name} was {@code null}.
     */
    FutureResult<SearchResultEntry> readEntryAsync(DN name,
            Collection<String> attributeDescriptions,
            ResultHandler<? super SearchResultEntry> handler);
    FutureResult<SearchResultEntry> readEntryAsync(DN name, Collection<String> attributeDescriptions);
    /**
     * Removes the provided connection event listener from this connection so
@@ -1231,11 +1329,7 @@
     *
     * @param request
     *            The search request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param resultHandler
     * @param entryHandler
     *            A search result handler which can be used to asynchronously
     *            process the search result entries and references as they are
     *            received, may be {@code null}.
@@ -1248,9 +1342,33 @@
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> searchAsync(SearchRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            SearchResultHandler resultHandler);
    FutureResult<Result> searchAsync(SearchRequest request, SearchResultHandler entryHandler);
    /**
     * Asynchronously searches the Directory Server using the provided search
     * request.
     *
     * @param request
     *            The search request.
     * @param intermediateResponseHandler
     *            An intermediate response handler which can be used to process
     *            any intermediate responses as they are received, may be
     *            {@code null}.
     * @param entryHandler
     *            A search result handler which can be used to asynchronously
     *            process the search result entries and references as they are
     *            received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support search operations.
     * @throws IllegalStateException
     *             If this connection has already been closed, i.e. if
     *             {@code isClosed() == true}.
     * @throws NullPointerException
     *             If {@code request} was {@code null}.
     */
    FutureResult<Result> searchAsync(SearchRequest request, IntermediateResponseHandler intermediateResponseHandler,
        SearchResultHandler entryHandler);
    /**
     * Searches the Directory Server for a single entry using the provided
@@ -1337,9 +1455,6 @@
     *
     * @param request
     *            The search request.
     * @param handler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the result of the operation.
     * @throws UnsupportedOperationException
     *             If this connection does not support search operations.
@@ -1349,6 +1464,5 @@
     * @throws NullPointerException
     *             If the {@code request} was {@code null}.
     */
    FutureResult<SearchResultEntry> searchSingleEntryAsync(SearchRequest request,
            ResultHandler<? super SearchResultEntry> handler);
    FutureResult<SearchResultEntry> searchSingleEntryAsync(SearchRequest request);
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/ConnectionFactory.java
@@ -22,13 +22,15 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import java.io.Closeable;
import org.forgerock.util.promise.Promise;
/**
 * A connection factory provides an interface for obtaining a connection to a
 * Directory Server. Connection factories can be used to wrap other connection
@@ -75,17 +77,12 @@
    /**
     * Asynchronously obtains a connection to the Directory Server associated
     * with this connection factory. The returned {@code FutureResult} can be
     * used to retrieve the completed connection. Alternatively, if a
     * {@code ResultHandler} is provided, the handler will be notified when the
     * connection is available and ready for use.
     * with this connection factory. The returned {@code Promise} can be used to
     * retrieve the completed connection.
     *
     * @param handler
     *            The completion handler, or {@code null} if no handler is to be
     *            used.
     * @return A future which can be used to retrieve the connection.
     * @return A promise which can be used to retrieve the connection.
     */
    FutureResult<Connection> getConnectionAsync(ResultHandler<? super Connection> handler);
    Promise<Connection, ErrorResultException> getConnectionAsync();
    /**
     * Returns a connection to the Directory Server associated with this
opendj-core/src/main/java/org/forgerock/opendj/ldap/ConnectionPool.java
@@ -26,6 +26,8 @@
package org.forgerock.opendj.ldap;
import org.forgerock.util.promise.Promise;
/**
 * A connection factory which maintains and re-uses a pool of connections.
 * Connections obtained from a connection pool are returned to the connection
@@ -51,28 +53,25 @@
     * Calling {@code close} on a connection pool which is already closed has no
     * effect.
     */
    @Override
    void close();
    /**
     * Asynchronously obtains a connection from this connection pool,
     * potentially opening a new connection if needed.
     * <p>
     * The returned {@code FutureResult} can be used to retrieve the pooled
     * connection. Alternatively, if a {@code ResultHandler} is provided, the
     * handler will be notified when the pooled connection is available and
     * ready for use.
     * The returned {@code Promise} can be used to retrieve the pooled
     * connection.
     * <p>
     * Closing the pooled connection will, depending on the connection pool
     * implementation, return the connection to this pool without closing it.
     *
     * @param handler
     *            The completion handler, or {@code null} if no handler is to be
     *            used.
     * @return A future which can be used to retrieve the pooled connection.
     * @return A promise which can be used to retrieve the pooled connection.
     * @throws IllegalStateException
     *             If this connection pool has already been closed.
     */
    FutureResult<Connection> getConnectionAsync(ResultHandler<? super Connection> handler);
    @Override
    Promise<Connection, ErrorResultException> getConnectionAsync();
    /**
     * Obtains a connection from this connection pool, potentially opening a new
@@ -87,5 +86,6 @@
     * @throws IllegalStateException
     *             If this connection pool has already been closed.
     */
    @Override
    Connection getConnection() throws ErrorResultException;
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java
@@ -37,6 +37,7 @@
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Promise;
/**
 * This class contains methods for creating and manipulating connection
@@ -547,9 +548,8 @@
            }
            @Override
            public FutureResult<Connection> getConnectionAsync(
                    final ResultHandler<? super Connection> handler) {
                return factory.getConnectionAsync(handler);
            public Promise<Connection, ErrorResultException> getConnectionAsync() {
                return factory.getConnectionAsync();
            }
            @Override
@@ -642,6 +642,7 @@
                // Do nothing.
            }
            @Override
            public void close(org.forgerock.opendj.ldap.requests.UnbindRequest request,
                    String reason) {
                // Do nothing.
@@ -662,9 +663,8 @@
        return new ConnectionFactory() {
            @Override
            public FutureResult<Connection> getConnectionAsync(
                    ResultHandler<? super Connection> handler) {
                return factory.getConnectionAsync(handler);
            public Promise<Connection, ErrorResultException> getConnectionAsync() {
                return factory.getConnectionAsync();
            }
            @Override
opendj-core/src/main/java/org/forgerock/opendj/ldap/FutureResult.java
@@ -22,80 +22,20 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2014 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.forgerock.util.promise.Promise;
/**
 * A handle which can be used to retrieve the Result of an asynchronous Request.
 *
 * @param <S>
 *            The type of result returned by this future.
 *            The type of result returned by this future result.
 */
public interface FutureResult<S> extends Future<S> {
    /**
     * Attempts to cancel the request. This attempt will fail if the request has
     * already completed or has already been cancelled. If successful, then
     * cancellation results in an abandon or cancel request (if configured)
     * being sent to the server.
     * <p>
     * After this method returns, subsequent calls to {@link #isDone} will
     * always return {@code true}. Subsequent calls to {@link #isCancelled} will
     * always return {@code true} if this method returned {@code true}.
     *
     * @param mayInterruptIfRunning
     *            {@code true} if the thread executing executing the response
     *            handler should be interrupted; otherwise, in-progress response
     *            handlers are allowed to complete.
     * @return {@code false} if the request could not be cancelled, typically
     *         because it has already completed normally; {@code true}
     *         otherwise.
     */
    boolean cancel(boolean mayInterruptIfRunning);
    /**
     * Waits if necessary for the request to complete, and then returns the
     * result if the request succeeded. If the request failed (i.e. a
     * non-successful result code was obtained) then the result is thrown as an
     * {@link ErrorResultException}.
     *
     * @return The result, but only if the result code indicates that the
     *         request succeeded.
     * @throws ErrorResultException
     *             If the result code indicates that the request failed for some
     *             reason.
     * @throws InterruptedException
     *             If the current thread was interrupted while waiting.
     */
    S get() throws ErrorResultException, InterruptedException;
    /**
     * Waits if necessary for at most the given time for the request to
     * complete, and then returns the result if the request succeeded. If the
     * request failed (i.e. a non-successful result code was obtained) then the
     * result is thrown as an {@link ErrorResultException}.
     *
     * @param timeout
     *            The maximum time to wait.
     * @param unit
     *            The time unit of the timeout argument.
     * @return The result, but only if the result code indicates that the
     *         request succeeded.
     * @throws ErrorResultException
     *             If the result code indicates that the request failed for some
     *             reason.
     * @throws TimeoutException
     *             If the wait timed out.
     * @throws InterruptedException
     *             If the current thread was interrupted while waiting.
     */
    S get(long timeout, TimeUnit unit) throws ErrorResultException, TimeoutException,
            InterruptedException;
public interface FutureResult<S> extends Promise<S, ErrorResultException> {
    /**
     * Returns the request ID of the request if appropriate.
     *
@@ -103,23 +43,4 @@
     */
    int getRequestID();
    /**
     * Returns {@code true} if the request was cancelled before it completed
     * normally.
     *
     * @return {@code true} if the request was cancelled before it completed
     *         normally, otherwise {@code false}.
     */
    boolean isCancelled();
    /**
     * Returns {@code true} if the request has completed.
     * <p>
     * Completion may be due to normal termination, an exception, or
     * cancellation. In all of these cases, this method will return {@code true}.
     *
     * @return {@code true} if the request has completed, otherwise
     *         {@code false}.
     */
    boolean isDone();
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/FutureResultImpl.java
New file
@@ -0,0 +1,64 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import org.forgerock.util.promise.PromiseImpl;
/**
 * This class provides an implementation of the {@code FutureResult}.
 *
 * @param <R>
 *            The type of result returned by this future.
 * @see Promise
 * @see Promises
 */
public class FutureResultImpl<R> extends PromiseImpl<R, ErrorResultException>
    implements FutureResult<R>, ResultHandler<R> {
    private final int requestID;
    /**
     * Creates a new future result with a request ID of -1.
     */
    public FutureResultImpl() {
        this(-1);
    }
    /**
     * Creates a future result with the provided request ID.
     *
     * @param requestID
     *            The request ID which will be returned by the default
     *            implementation of {@link #getRequestID}.
     */
    public FutureResultImpl(int requestID) {
        this.requestID = requestID;
    }
    @Override
    public int getRequestID() {
        return requestID;
    }
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/FutureResultWrapper.java
New file
@@ -0,0 +1,281 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.Function;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.Promises;
import org.forgerock.util.promise.SuccessHandler;
/**
 * This class is a {@link Promise} wrapper which implements {@link FutureResult} interface.
 *
 * It allows client code to return {@link FutureResult} instance when using {@link Promise}
 * chaining methods (e.g onSuccess(), then(), thenAsync()).
 *
 * Wrapping is specially needed with {@link Promise} method which are not returning
 * the original promise (i.e this) but a new one.
 *
 * It also provides some useful methods to create completed
 * {@link FutureResult} instance.
 *
 *
 * @param <R>
 *            The type of the task's result, or {@link Void} if the task does
 *            not return anything (i.e. it only has side-effects).
 */
public final class FutureResultWrapper<R> {
    private static class LdapPromiseWrapper<R> implements FutureResult<R> {
        private final Promise<R, ErrorResultException> wrappedPromise;
        private final int requestID;
        LdapPromiseWrapper(Promise<R, ErrorResultException> wrappedPromise, int requestID) {
            this.wrappedPromise = wrappedPromise;
            this.requestID = requestID;
        }
        @Override
        public int getRequestID() {
            return wrappedPromise instanceof FutureResult ? ((FutureResult<R>) wrappedPromise).getRequestID()
                : requestID;
        }
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return wrappedPromise.cancel(mayInterruptIfRunning);
        }
        @Override
        public R get() throws ExecutionException, InterruptedException {
            return wrappedPromise.get();
        }
        @Override
        public R get(long timeout, TimeUnit unit) throws ExecutionException, TimeoutException, InterruptedException {
            return wrappedPromise.get(timeout, unit);
        }
        @Override
        public R getOrThrow() throws InterruptedException, ErrorResultException {
            return wrappedPromise.getOrThrow();
        }
        @Override
        public R getOrThrow(long timeout, TimeUnit unit) throws InterruptedException, ErrorResultException,
            TimeoutException {
            return wrappedPromise.getOrThrow(timeout, unit);
        }
        @Override
        public R getOrThrowUninterruptibly() throws ErrorResultException {
            return wrappedPromise.getOrThrowUninterruptibly();
        }
        @Override
        public R getOrThrowUninterruptibly(long timeout, TimeUnit unit) throws ErrorResultException, TimeoutException {
            return wrappedPromise.getOrThrowUninterruptibly(timeout, unit);
        }
        @Override
        public boolean isCancelled() {
            return wrappedPromise.isCancelled();
        }
        @Override
        public boolean isDone() {
            return wrappedPromise.isDone();
        }
        @Override
        public Promise<R, ErrorResultException> onFailure(FailureHandler<? super ErrorResultException> onFailure) {
            wrappedPromise.onFailure(onFailure);
            return this;
        }
        @Override
        public Promise<R, ErrorResultException> onSuccess(SuccessHandler<? super R> onSuccess) {
            wrappedPromise.onSuccess(onSuccess);
            return this;
        }
        @Override
        public Promise<R, ErrorResultException> onSuccessOrFailure(Runnable onSuccessOrFailure) {
            wrappedPromise.onSuccessOrFailure(onSuccessOrFailure);
            return this;
        }
        @Override
        // @Checkstyle:ignore
        public <VOUT> Promise<VOUT, ErrorResultException> then(
                Function<? super R, VOUT, ErrorResultException> onSuccess) {
            return new LdapPromiseWrapper<VOUT>(wrappedPromise.then(onSuccess), getRequestID());
        }
        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override
        // @Checkstyle:ignore
        public <VOUT, EOUT extends Exception> Promise<VOUT, EOUT> then(Function<? super R, VOUT, EOUT> onSuccess,
                Function<? super ErrorResultException, VOUT, EOUT> onFailure) {
            return new LdapPromiseWrapper(wrappedPromise.then(onSuccess, onFailure), getRequestID());
        }
        @Override
        public Promise<R, ErrorResultException> then(SuccessHandler<? super R> onSuccess) {
            wrappedPromise.then(onSuccess);
            return this;
        }
        @Override
        public Promise<R, ErrorResultException> then(SuccessHandler<? super R> onSuccess,
            FailureHandler<? super ErrorResultException> onFailure) {
            wrappedPromise.then(onSuccess, onFailure);
            return this;
        }
        @Override
        public Promise<R, ErrorResultException> thenAlways(Runnable onSuccessOrFailure) {
            wrappedPromise.thenAlways(onSuccessOrFailure);
            return this;
        }
        @Override
        // @Checkstyle:ignore
        public <VOUT> Promise<VOUT, ErrorResultException> thenAsync(
                AsyncFunction<? super R, VOUT, ErrorResultException> onSuccess) {
            return new LdapPromiseWrapper<VOUT>(wrappedPromise.thenAsync(onSuccess), getRequestID());
        }
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override
        // @Checkstyle:ignore
        public <VOUT, EOUT extends Exception> Promise<VOUT, EOUT> thenAsync(
                AsyncFunction<? super R, VOUT, EOUT> onSuccess,
                AsyncFunction<? super ErrorResultException, VOUT, EOUT> onFailure) {
            return new LdapPromiseWrapper(wrappedPromise.thenAsync(onSuccess, onFailure), getRequestID());
        }
    }
    /**
     * Returns a {@link FutureResult} representing an asynchronous task which
     * has already succeeded with the provided result. Attempts to get the
     * result will immediately return the result.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param result
     *            The result of the asynchronous task.
     * @return A {@link FutureResult} representing an asynchronous task which
     *         has already succeeded with the provided result.
     */
    public static <R> FutureResult<R> newSuccessfulFutureResult(final R result) {
        return new LdapPromiseWrapper<R>(Promises.<R, ErrorResultException> newSuccessfulPromise(result), -1);
    }
    /**
     * Returns a {@link FutureResult} representing an asynchronous task,
     * identified by the provided requestID, which has already succeeded with
     * the provided result. Attempts to get the result will immediately return
     * the result.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param result
     *            The result of the asynchronous task.
     * @param requestID
     *            The request ID of the succeeded task.
     * @return A {@link FutureResult} representing an asynchronous task which
     *         has already succeeded with the provided result.
     */
    public static <R> FutureResult<R> newSuccessfulFutureResult(final R result, int requestID) {
        return new LdapPromiseWrapper<R>(Promises.<R, ErrorResultException> newSuccessfulPromise(result), requestID);
    }
    /**
     * Returns a {@link FutureResult} representing an asynchronous task which
     * has already failed with the provided error.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param <E>
     *            The type of the exception thrown by the task if it fails.
     * @param error
     *            The exception indicating why the asynchronous task has failed.
     * @return A {@link FutureResult} representing an asynchronous task which
     *         has already failed with the provided error.
     */
    public static <R, E extends ErrorResultException> FutureResult<R> newFailedFutureResult(final E error) {
        return new LdapPromiseWrapper<R>(Promises.<R, ErrorResultException> newFailedPromise(error), -1);
    }
    /**
     * Returns a {@link FutureResult} representing an asynchronous task,
     * identified by the provided requestID, which has already failed with the
     * provided error.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param <E>
     *            The type of the exception thrown by the task if it fails.
     * @param error
     *            The exception indicating why the asynchronous task has failed.
     * @param requestID
     *            The request ID of the failed task.
     * @return A {@link FutureResult} representing an asynchronous task which
     *         has already failed with the provided error.
     */
    public static <R, E extends ErrorResultException> FutureResult<R> newFailedFutureResult(final E error,
            int requestID) {
        return new LdapPromiseWrapper<R>(Promises.<R, ErrorResultException> newFailedPromise(error), requestID);
    }
    /**
     * Converts a {@link Promise} to a {@link FutureResult}.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param wrappedPromise
     *            The {@link Promise} to wrap.
     * @return A {@link FutureResult} representing the same asynchronous task as
     *         the {@link Promise} provided.
     */
    public static <R> FutureResult<R> asFutureResult(Promise<R, ErrorResultException> wrappedPromise) {
        return new LdapPromiseWrapper<R>(wrappedPromise, -1);
    }
    private FutureResultWrapper() {
    }
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java
@@ -26,15 +26,12 @@
 */
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.DEFAULT_SCHEDULER;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -65,14 +62,20 @@
import org.forgerock.opendj.ldap.responses.SearchResultReference;
import org.forgerock.opendj.ldap.spi.ConnectionState;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.Function;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.SuccessHandler;
import com.forgerock.opendj.util.AsynchronousFutureResult;
import com.forgerock.opendj.util.CompletedFutureResult;
import com.forgerock.opendj.util.FutureResultTransformer;
import com.forgerock.opendj.util.RecursiveFutureResult;
import com.forgerock.opendj.util.ReferenceCountedObject;
import com.forgerock.opendj.util.TimeSource;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import static com.forgerock.opendj.util.StaticUtils.*;
/**
 * An heart beat connection factory can be used to create connections that sends
 * a periodic search request to a Directory Server.
@@ -109,122 +112,58 @@
     * to a {@code ConnectionImpl} and registering it in the table of valid
     * connections.
     */
    private final class ConnectionFutureResultImpl {
    private final class ConnectionFutureResultImpl implements Runnable {
        private Connection connection;
        private Connection heartBeatConnection;
        private ErrorResultException heartBeatError;
        /**
         * This class handles the initial heart beat result notification or
         * timeout. We need to take care to avoid processing multiple results,
         * which may occur when the heart beat is timed out and a result follows
         * soon after, or vice versa.
         * Due to a potential race between the heart beat timing out and the
         * heart beat completing this atomic ensures that notification only
         * occurs once.
         */
        private final class InitialHeartBeatResultHandler implements SearchResultHandler, Runnable {
            private final ResultHandler<? super Result> handler;
        private final AtomicBoolean isComplete = new AtomicBoolean();
            /**
             * Due to a potential race between the heart beat timing out and the
             * heart beat completing this atomic ensures that notification only
             * occurs once.
             */
            private final AtomicBoolean isComplete = new AtomicBoolean();
        private final Function<Result, Connection, ErrorResultException> futureSearchSuccess;
        private final Function<ErrorResultException, Connection, ErrorResultException> futureSearchError;
            private InitialHeartBeatResultHandler(final ResultHandler<? super Result> handler) {
                this.handler = handler;
            }
            @Override
            public boolean handleEntry(final SearchResultEntry entry) {
                /*
                 * Depending on the configuration, a heartbeat may return some
                 * entries. However, we can just ignore them.
                 */
                return true;
            }
            @Override
            public void handleErrorResult(final ErrorResultException error) {
                if (isComplete.compareAndSet(false, true)) {
                    handler.handleErrorResult(error);
                }
            }
            @Override
            public boolean handleReference(final SearchResultReference reference) {
                /*
                 * Depending on the configuration, a heartbeat may return some
                 * references. However, we can just ignore them.
                 */
                return true;
            }
            @Override
            public void handleResult(final Result result) {
                if (isComplete.compareAndSet(false, true)) {
                    handler.handleResult(result);
                }
            }
            /*
             * Invoked by the scheduler when the heart beat times out.
             */
            @Override
            public void run() {
                handleErrorResult(newHeartBeatTimeoutError());
            }
        }
        private Connection connection;
        private final RecursiveFutureResult<Connection, Result> futureConnectionResult;
        private final FutureResultTransformer<Result, Connection> futureSearchResult;
        private ConnectionFutureResultImpl(final ResultHandler<? super Connection> handler) {
            // Create a future which will handle the initial heart beat result.
            this.futureSearchResult = new FutureResultTransformer<Result, Connection>(handler) {
        private ConnectionFutureResultImpl() {
            this.futureSearchSuccess = new Function<Result, Connection, ErrorResultException>() {
                @Override
                protected ErrorResultException transformErrorResult(
                        final ErrorResultException errorResult) {
                    // Ensure that the connection is closed.
                    if (connection != null) {
                        connection.close();
                        connection = null;
                public Connection apply(Result result) throws ErrorResultException {
                    if (isComplete.compareAndSet(false, true)) {
                        heartBeatConnection = adaptConnection(connection);
                    }
                    releaseScheduler();
                    return adaptHeartBeatError(errorResult);
                }
                @Override
                protected Connection transformResult(final Result result)
                        throws ErrorResultException {
                    return adaptConnection(connection);
                    return heartBeatConnection;
                }
            };
            // Create a future which will handle connection result.
            this.futureConnectionResult =
                    new RecursiveFutureResult<Connection, Result>(futureSearchResult) {
                        @Override
                        protected FutureResult<? extends Result> chainResult(
                                final Connection innerResult,
                                final ResultHandler<? super Result> handler)
                                throws ErrorResultException {
                            // Save the connection for later once the heart beat completes.
                            connection = innerResult;
            this.futureSearchError = new Function<ErrorResultException, Connection, ErrorResultException>() {
                @Override
                public Connection apply(ErrorResultException error) throws ErrorResultException {
                    manageError(error);
                    throw heartBeatError;
                }
            };
        }
                            /*
                             * Send the initial heart beat and schedule a client
                             * side timeout notification.
                             */
                            final InitialHeartBeatResultHandler wrappedHandler =
                                    new InitialHeartBeatResultHandler(handler);
                            scheduler.get().schedule(wrappedHandler, timeoutMS,
                                    TimeUnit.MILLISECONDS);
                            return connection.searchAsync(heartBeatRequest, null, wrappedHandler);
                        }
                    };
        @Override
        public void run() {
            manageError(newHeartBeatTimeoutError());
        }
            // Link the two futures.
            futureSearchResult.setFutureResult(futureConnectionResult);
        private void manageError(ErrorResultException error) {
            if (isComplete.compareAndSet(false, true)) {
                // Ensure that the connection is closed.
                if (connection != null) {
                    connection.close();
                    connection = null;
                }
                releaseScheduler();
                heartBeatError = adaptHeartBeatError(error);
            }
        }
    }
@@ -241,105 +180,38 @@
         * completed requests are removed from the {@code pendingResults} queue,
         * as well as ensuring that requests are only completed once.
         */
        private abstract class AbstractWrappedResultHandler<R, H extends ResultHandler<? super R>>
                implements ResultHandler<R>, FutureResult<R> {
            /** The user provided result handler. */
            protected final H handler;
            private final CountDownLatch completed = new CountDownLatch(1);
            private ErrorResultException error;
            private FutureResult<R> innerFuture;
            private R result;
            AbstractWrappedResultHandler(final H handler) {
                this.handler = handler;
            }
        private abstract class AbstractWrappedResultHandler<R> implements ResultHandler<R> {
            private final AtomicBoolean completed = new AtomicBoolean();
            @Override
            public boolean cancel(final boolean mayInterruptIfRunning) {
                return innerFuture.cancel(mayInterruptIfRunning);
            }
            @Override
            public R get() throws ErrorResultException, InterruptedException {
                completed.await();
                return get0();
            }
            @Override
            public R get(final long timeout, final TimeUnit unit) throws ErrorResultException,
                    TimeoutException, InterruptedException {
                if (completed.await(timeout, unit)) {
                    return get0();
                } else {
                    throw new TimeoutException();
                }
            }
            @Override
            public int getRequestID() {
                return innerFuture.getRequestID();
            }
            @Override
            public void handleErrorResult(final ErrorResultException error) {
                if (tryComplete(null, error)) {
                    if (handler != null) {
                        handler.handleErrorResult(timestamp(error));
                    } else {
                        timestamp(error);
                    }
            public void handleError(final ErrorResultException error) {
                if (tryComplete()) {
                    timestamp(error);
                }
            }
            @Override
            public void handleResult(final R result) {
                if (tryComplete(result, null)) {
                    if (handler != null) {
                        handler.handleResult(timestamp(result));
                    } else {
                        timestamp(result);
                    }
                if (tryComplete()) {
                    timestamp(result);
                }
            }
            @Override
            public boolean isCancelled() {
                return innerFuture.isCancelled();
            }
            @Override
            public boolean isDone() {
                return completed.getCount() == 0;
                return completed.get();
            }
            abstract void releaseBindOrStartTLSLockIfNeeded();
            FutureResult<R> setInnerFuture(final FutureResult<R> innerFuture) {
                this.innerFuture = innerFuture;
                return this;
            }
            private R get0() throws ErrorResultException {
                if (result != null) {
                    return result;
                } else {
                    throw error;
                }
            }
            /**
             * Attempts to complete this request, returning true if successful.
             * This method is synchronized in order to avoid race conditions
             * with search result processing.
             */
            private synchronized boolean tryComplete(final R result,
                    final ErrorResultException error) {
            private synchronized boolean tryComplete() {
                if (pendingResults.remove(this)) {
                    this.result = result;
                    this.error = error;
                    completed.countDown();
                    releaseBindOrStartTLSLockIfNeeded();
                    completed.set(true);
                    return true;
                } else {
                    return false;
@@ -355,14 +227,9 @@
         * @param <R>
         *            The type of result returned by the request.
         */
        private abstract class DelayedFuture<R extends Result> extends
                AsynchronousFutureResult<R, ResultHandler<? super R>> implements Runnable {
        private abstract class DelayedFuture<R extends Result> extends FutureResultImpl<R> implements Runnable {
            private volatile FutureResult<R> innerFuture = null;
            protected DelayedFuture(final ResultHandler<? super R> handler) {
                super(handler);
            }
            @Override
            public final int getRequestID() {
                return innerFuture != null ? innerFuture.getRequestID() : -1;
@@ -382,8 +249,7 @@
            protected abstract FutureResult<R> dispatch();
            @Override
            protected final ErrorResultException handleCancelRequest(
                    final boolean mayInterruptIfRunning) {
            protected final ErrorResultException tryCancel(final boolean mayInterruptIfRunning) {
                if (innerFuture != null) {
                    innerFuture.cancel(mayInterruptIfRunning);
                }
@@ -395,12 +261,7 @@
         * A result handler wrapper for bind or startTLS requests which releases
         * the bind/startTLS lock on completion.
         */
        private final class WrappedBindOrStartTLSResultHandler<R> extends
                AbstractWrappedResultHandler<R, ResultHandler<? super R>> {
            WrappedBindOrStartTLSResultHandler(final ResultHandler<? super R> handler) {
                super(handler);
            }
        private final class WrappedBindOrStartTLSResultHandler<R> extends AbstractWrappedResultHandler<R> {
            @Override
            void releaseBindOrStartTLSLockIfNeeded() {
                releaseBindOrStartTLSLock();
@@ -411,12 +272,7 @@
         * A result handler wrapper for normal requests which does not release
         * the bind/startTLS lock on completion.
         */
        private final class WrappedResultHandler<R> extends
                AbstractWrappedResultHandler<R, ResultHandler<? super R>> {
            WrappedResultHandler(final ResultHandler<? super R> handler) {
                super(handler);
            }
        private final class WrappedResultHandler<R> extends AbstractWrappedResultHandler<R> {
            @Override
            void releaseBindOrStartTLSLockIfNeeded() {
                // No-op for normal operations.
@@ -428,18 +284,19 @@
         * results are not sent once the request has been completed (see
         * markComplete()).
         */
        private final class WrappedSearchResultHandler extends
                AbstractWrappedResultHandler<Result, SearchResultHandler> implements
        private final class WrappedSearchResultHandler extends AbstractWrappedResultHandler<Result> implements
                SearchResultHandler {
            private final SearchResultHandler entryHandler;
            WrappedSearchResultHandler(final SearchResultHandler handler) {
                super(handler);
                this.entryHandler = handler;
            }
            @Override
            public synchronized boolean handleEntry(final SearchResultEntry entry) {
                if (!isDone()) {
                    if (handler != null) {
                        handler.handleEntry(timestamp(entry));
                    if (entryHandler != null) {
                        entryHandler.handleEntry(timestamp(entry));
                    } else {
                        timestamp(entry);
                    }
@@ -452,8 +309,8 @@
            @Override
            public synchronized boolean handleReference(final SearchResultReference reference) {
                if (!isDone()) {
                    if (handler != null) {
                        handler.handleReference(timestamp(reference));
                    if (entryHandler != null) {
                        entryHandler.handleReference(timestamp(reference));
                    } else {
                        timestamp(reference);
                    }
@@ -475,44 +332,17 @@
        /**
         * Search result handler for processing heart beat responses.
         */
        private final SearchResultHandler heartBeatHandler = new SearchResultHandler() {
        private final SearchResultHandler heartBeatEntryHandler = new SearchResultHandler() {
            @Override
            public boolean handleEntry(final SearchResultEntry entry) {
                timestamp(entry);
                return true;
            }
            @Override
            public void handleErrorResult(final ErrorResultException error) {
                /*
                 * Connection failure will be handled by connection event
                 * listener. Ignore cancellation errors since these indicate
                 * that the heart beat was aborted by a client-side close.
                 */
                if (!(error instanceof CancelledResultException)) {
                    /*
                     * Log at debug level to avoid polluting the logs with
                     * benign password policy related errors. See OPENDJ-1168
                     * and OPENDJ-1167.
                     */
                    logger.debug(LocalizableMessage.raw("Heartbeat failed for connection factory '%s'", factory,
                            error));
                    timestamp(error);
                }
                releaseHeartBeatLock();
            }
            @Override
            public boolean handleReference(final SearchResultReference reference) {
                timestamp(reference);
                return true;
            }
            @Override
            public void handleResult(final Result result) {
                timestamp(result);
                releaseHeartBeatLock();
            }
        };
        /**
@@ -527,8 +357,8 @@
         * signalled if no heart beat is detected within the permitted timeout
         * period.
         */
        private final Queue<AbstractWrappedResultHandler<?, ?>> pendingResults =
                new ConcurrentLinkedQueue<AbstractWrappedResultHandler<?, ?>>();
        private final Queue<AbstractWrappedResultHandler<?>> pendingResults =
                new ConcurrentLinkedQueue<AbstractWrappedResultHandler<?>>();
        /** Internal connection state. */
        private final ConnectionState state = new ConnectionState();
@@ -554,11 +384,9 @@
        @Override
        public FutureResult<Result> addAsync(final AddRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            if (checkState(resultHandler)) {
                final WrappedResultHandler<Result> h = wrap(resultHandler);
                return checkState(connection.addAsync(request, intermediateResponseHandler, h), h);
                final IntermediateResponseHandler intermediateResponseHandler) {
            if (checkState()) {
                return then(connection.addAsync(request, intermediateResponseHandler), createResultHandler());
            } else {
                return newConnectionErrorFuture();
            }
@@ -571,31 +399,24 @@
        @Override
        public FutureResult<BindResult> bindAsync(final BindRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super BindResult> resultHandler) {
            if (checkState(resultHandler)) {
                final IntermediateResponseHandler intermediateResponseHandler) {
            if (checkState()) {
                if (sync.tryLockShared()) {
                    // Fast path
                    final WrappedBindOrStartTLSResultHandler<BindResult> h =
                            wrapForBindOrStartTLS(resultHandler);
                    return checkState(
                            connection.bindAsync(request, intermediateResponseHandler, h), h);
                    return then(connection.bindAsync(request, intermediateResponseHandler), wrapForBindOrStartTLS());
                } else {
                    /*
                     * A heart beat must be in progress so create a runnable
                     * task which will be executed when the heart beat
                     * completes.
                     */
                    final DelayedFuture<BindResult> future =
                            new DelayedFuture<BindResult>(resultHandler) {
                                @Override
                                public FutureResult<BindResult> dispatch() {
                                    final WrappedBindOrStartTLSResultHandler<BindResult> h =
                                            wrapForBindOrStartTLS(this);
                                    return checkState(connection.bindAsync(request,
                                            intermediateResponseHandler, h), h);
                                }
                            };
                    final DelayedFuture<BindResult> future = new DelayedFuture<BindResult>() {
                        @Override
                        public FutureResult<BindResult> dispatch() {
                            return HeartBeatConnectionFactory.this.then(
                                connection.bindAsync(request, intermediateResponseHandler), wrapForBindOrStartTLS());
                        }
                    };
                    /*
                     * Enqueue and flush if the heart beat has completed in the
                     * mean time.
@@ -623,12 +444,9 @@
        @Override
        public FutureResult<CompareResult> compareAsync(final CompareRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super CompareResult> resultHandler) {
            if (checkState(resultHandler)) {
                final WrappedResultHandler<CompareResult> h = wrap(resultHandler);
                return checkState(connection.compareAsync(request, intermediateResponseHandler, h),
                        h);
                final IntermediateResponseHandler intermediateResponseHandler) {
            if (checkState()) {
                return then(connection.compareAsync(request, intermediateResponseHandler), createResultHandler());
            } else {
                return newConnectionErrorFuture();
            }
@@ -636,43 +454,35 @@
        @Override
        public FutureResult<Result> deleteAsync(final DeleteRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            if (checkState(resultHandler)) {
                final WrappedResultHandler<Result> h = wrap(resultHandler);
                return checkState(connection.deleteAsync(request, intermediateResponseHandler, h),
                        h);
                final IntermediateResponseHandler intermediateResponseHandler) {
            if (checkState()) {
                return then(connection.deleteAsync(request, intermediateResponseHandler), createResultHandler());
            } else {
                return newConnectionErrorFuture();
            }
        }
        @Override
        public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
                final ExtendedRequest<R> request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super R> resultHandler) {
            if (checkState(resultHandler)) {
        public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(final ExtendedRequest<R> request,
                final IntermediateResponseHandler intermediateResponseHandler) {
            if (checkState()) {
                if (isStartTLSRequest(request)) {
                    if (sync.tryLockShared()) {
                        // Fast path
                        final WrappedBindOrStartTLSResultHandler<R> h =
                                wrapForBindOrStartTLS(resultHandler);
                        return checkState(connection.extendedRequestAsync(request,
                                intermediateResponseHandler, h), h);
                        return then(connection.extendedRequestAsync(request, intermediateResponseHandler),
                                wrapForBindOrStartTLS());
                    } else {
                        /*
                         * A heart beat must be in progress so create a runnable
                         * task which will be executed when the heart beat
                         * completes.
                         */
                        final DelayedFuture<R> future = new DelayedFuture<R>(resultHandler) {
                        final DelayedFuture<R> future = new DelayedFuture<R>() {
                            @Override
                            public FutureResult<R> dispatch() {
                                final WrappedBindOrStartTLSResultHandler<R> h =
                                        wrapForBindOrStartTLS(this);
                                return checkState(connection.extendedRequestAsync(request,
                                        intermediateResponseHandler, h), h);
                                return HeartBeatConnectionFactory.this.then(
                                        connection.extendedRequestAsync(request, intermediateResponseHandler),
                                        wrapForBindOrStartTLS());
                            }
                        };
@@ -685,9 +495,8 @@
                        return future;
                    }
                } else {
                    final WrappedResultHandler<R> h = wrap(resultHandler);
                    return checkState(connection.extendedRequestAsync(request,
                            intermediateResponseHandler, h), h);
                    return then(connection.extendedRequestAsync(request, intermediateResponseHandler),
                            createResultHandler());
                }
            } else {
                return newConnectionErrorFuture();
@@ -740,12 +549,9 @@
        @Override
        public FutureResult<Result> modifyAsync(final ModifyRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            if (checkState(resultHandler)) {
                final WrappedResultHandler<Result> h = wrap(resultHandler);
                return checkState(connection.modifyAsync(request, intermediateResponseHandler, h),
                        h);
                final IntermediateResponseHandler intermediateResponseHandler) {
            if (checkState()) {
                return then(connection.modifyAsync(request, intermediateResponseHandler), createResultHandler());
            } else {
                return newConnectionErrorFuture();
            }
@@ -753,12 +559,9 @@
        @Override
        public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            if (checkState(resultHandler)) {
                final WrappedResultHandler<Result> h = wrap(resultHandler);
                return checkState(
                        connection.modifyDNAsync(request, intermediateResponseHandler, h), h);
                final IntermediateResponseHandler intermediateResponseHandler) {
            if (checkState()) {
                return then(connection.modifyDNAsync(request, intermediateResponseHandler), createResultHandler());
            } else {
                return newConnectionErrorFuture();
            }
@@ -772,11 +575,11 @@
        @Override
        public FutureResult<Result> searchAsync(final SearchRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final SearchResultHandler resultHandler) {
            if (checkState(resultHandler)) {
                final WrappedSearchResultHandler h = wrap(resultHandler);
                return checkState(connection.searchAsync(request, intermediateResponseHandler, h),
                        h);
                final SearchResultHandler searchHandler) {
            if (checkState()) {
                final WrappedSearchResultHandler entryHandler = wrap(searchHandler);
                return then(connection.searchAsync(request, intermediateResponseHandler, entryHandler),
                        createResultHandler());
            } else {
                return newConnectionErrorFuture();
            }
@@ -809,23 +612,8 @@
            }
        }
        private <R> FutureResult<R> checkState(final FutureResult<R> future,
                final AbstractWrappedResultHandler<R, ? extends ResultHandler<? super R>> h) {
            h.setInnerFuture(future);
            checkState(h);
            return h;
        }
        private boolean checkState(final ResultHandler<?> h) {
            final ErrorResultException error = state.getConnectionError();
            if (error != null) {
                if (h != null) {
                    h.handleErrorResult(error);
                }
                return false;
            } else {
                return true;
            }
        private boolean checkState() {
            return state.getConnectionError() == null;
        }
        private void failPendingResults(final ErrorResultException error) {
@@ -833,9 +621,9 @@
             * Peek instead of pool because notification is responsible for
             * removing the element from the queue.
             */
            AbstractWrappedResultHandler<?, ?> pendingResult;
            AbstractWrappedResultHandler<?> pendingResult;
            while ((pendingResult = pendingResults.peek()) != null) {
                pendingResult.handleErrorResult(error);
                pendingResult.handleError(error);
            }
        }
@@ -864,8 +652,8 @@
            return request.getOID().equals(StartTLSExtendedRequest.OID);
        }
        private <R> CompletedFutureResult<R> newConnectionErrorFuture() {
            return new CompletedFutureResult<R>(state.getConnectionError());
        private <R> FutureResult<R> newConnectionErrorFuture() {
            return FutureResultWrapper.newFailedFutureResult(state.getConnectionError());
        }
        private void releaseBindOrStartTLSLock() {
@@ -908,7 +696,39 @@
             */
            if (sync.tryLockExclusively()) {
                try {
                    connection.searchAsync(heartBeatRequest, null, heartBeatHandler);
                    FutureResult<Result> future = connection.searchAsync(heartBeatRequest, heartBeatEntryHandler);
                    if (future != null) {
                        future.onSuccess(new SuccessHandler<Result>() {
                            @Override
                            public void handleResult(Result result) {
                                timestamp(result);
                                releaseHeartBeatLock();
                            }
                        }).onFailure(new FailureHandler<ErrorResultException>() {
                            @Override
                            public void handleError(ErrorResultException error) {
                                /*
                                 * Connection failure will be handled by
                                 * connection event listener. Ignore
                                 * cancellation errors since these indicate that
                                 * the heart beat was aborted by a client-side
                                 * close.
                                 */
                                if (!(error instanceof CancelledResultException)) {
                                    /*
                                     * Log at debug level to avoid polluting the
                                     * logs with benign password policy related
                                     * errors. See OPENDJ-1168 and OPENDJ-1167.
                                     */
                                    logger.debug(LocalizableMessage.raw("Heartbeat failed for connection factory '%s'",
                                            factory, error));
                                    timestamp(error);
                                }
                                releaseHeartBeatLock();
                            }
                        });
                    }
                } catch (final IllegalStateException e) {
                    /*
                     * This may happen when we attempt to send the heart beat
@@ -938,8 +758,8 @@
            return response;
        }
        private <R> WrappedResultHandler<R> wrap(final ResultHandler<? super R> handler) {
            final WrappedResultHandler<R> h = new WrappedResultHandler<R>(handler);
        private <R> WrappedResultHandler<R> createResultHandler() {
            final WrappedResultHandler<R> h = new WrappedResultHandler<R>();
            pendingResults.add(h);
            return h;
        }
@@ -950,10 +770,8 @@
            return h;
        }
        private <R> WrappedBindOrStartTLSResultHandler<R> wrapForBindOrStartTLS(
                final ResultHandler<? super R> handler) {
            final WrappedBindOrStartTLSResultHandler<R> h =
                    new WrappedBindOrStartTLSResultHandler<R>(handler);
        private <R> WrappedBindOrStartTLSResultHandler<R> wrapForBindOrStartTLS() {
            final WrappedBindOrStartTLSResultHandler<R> h = new WrappedBindOrStartTLSResultHandler<R>();
            pendingResults.add(h);
            return h;
        }
@@ -1241,7 +1059,7 @@
        try {
            final Connection connection = factory.getConnection();
            try {
                connection.searchAsync(heartBeatRequest, null, null).get(timeoutMS,
                connection.searchAsync(heartBeatRequest, null).getOrThrow(timeoutMS,
                        TimeUnit.MILLISECONDS);
                succeeded = true;
                return adaptConnection(connection);
@@ -1260,22 +1078,24 @@
    }
    @Override
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> handler) {
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        acquireScheduler(); // Protect scheduler.
        // Create a future responsible for chaining the initial heartbeat search.
        final ConnectionFutureResultImpl compositeFuture = new ConnectionFutureResultImpl(handler);
        // Create a future responsible for chaining the initial heartbeat
        // search.
        final ConnectionFutureResultImpl compositeFuture = new ConnectionFutureResultImpl();
        // Request a connection.
        final FutureResult<Connection> connectionFuture =
                factory.getConnectionAsync(compositeFuture.futureConnectionResult);
        // Set the connection future in the composite so that the returned search future can delegate.
        compositeFuture.futureConnectionResult.setFutureResult(connectionFuture);
        // Return the future representing the heartbeat.
        return compositeFuture.futureSearchResult;
        // Request a connection and return the future representing the
        // heartbeat.
        return factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, ErrorResultException>() {
            @Override
            public Promise<Result, ErrorResultException> apply(final Connection connectionResult) {
                // Save the connection for later once the heart beat completes.
                compositeFuture.connection = connectionResult;
                scheduler.get().schedule(compositeFuture, timeoutMS, TimeUnit.MILLISECONDS);
                return connectionResult.searchAsync(heartBeatRequest, null);
            }
        }).then(compositeFuture.futureSearchSuccess, compositeFuture.futureSearchError);
    }
    @Override
@@ -1301,6 +1121,11 @@
        }
    }
    private <R extends Result> FutureResult<R> then(FutureResult<R> future,
            ResultHandler<? super Object> resultHandler) {
        return (FutureResult<R>) future.onSuccess(resultHandler).onFailure(resultHandler);
    }
    private ErrorResultException adaptHeartBeatError(final Exception error) {
        if (error instanceof ConnectionException) {
            return (ErrorResultException) error;
opendj-core/src/main/java/org/forgerock/opendj/ldap/InternalConnection.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
@@ -51,8 +51,6 @@
import org.forgerock.opendj.ldap.spi.LDAPExtendedFutureResultImpl;
import org.forgerock.opendj.ldap.spi.LDAPFutureResultImpl;
import org.forgerock.opendj.ldap.spi.LDAPSearchFutureResultImpl;
import com.forgerock.opendj.util.CompletedFutureResult;
import org.forgerock.util.Reject;
/**
@@ -66,10 +64,9 @@
        private final BindRequest bindRequest;
        InternalBindFutureResultImpl(final int messageID, final BindRequest bindRequest,
                final ResultHandler<? super BindResult> resultHandler,
                final IntermediateResponseHandler intermediateResponseHandler,
                final Connection connection) {
            super(messageID, resultHandler, intermediateResponseHandler, connection);
            super(messageID, intermediateResponseHandler, connection);
            this.bindRequest = bindRequest;
        }
@@ -117,7 +114,7 @@
    public FutureResult<Void> abandonAsync(final AbandonRequest request) {
        final int i = messageID.getAndIncrement();
        serverConnection.handleAbandon(i, request);
        return new CompletedFutureResult<Void>((Void) null, i);
        return FutureResultWrapper.newSuccessfulFutureResult((Void) null, i);
    }
    /**
@@ -125,12 +122,9 @@
     */
    @Override
    public FutureResult<Result> addAsync(final AddRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int i = messageID.getAndIncrement();
        final LDAPFutureResultImpl future =
                new LDAPFutureResultImpl(i, request, resultHandler, intermediateResponseHandler,
                        this);
        final LDAPFutureResultImpl future = new LDAPFutureResultImpl(i, request, intermediateResponseHandler, this);
        serverConnection.handleAdd(i, request, future, future);
        return future;
    }
@@ -149,12 +143,10 @@
     */
    @Override
    public FutureResult<BindResult> bindAsync(final BindRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super BindResult> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int i = messageID.getAndIncrement();
        final InternalBindFutureResultImpl future =
                new InternalBindFutureResultImpl(i, request, resultHandler,
                        intermediateResponseHandler, this);
        final InternalBindFutureResultImpl future = new InternalBindFutureResultImpl(i, request,
                intermediateResponseHandler, this);
        serverConnection.handleBind(i, 3, request, future, future);
        return future;
    }
@@ -173,12 +165,10 @@
     */
    @Override
    public FutureResult<CompareResult> compareAsync(final CompareRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super CompareResult> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int i = messageID.getAndIncrement();
        final LDAPCompareFutureResultImpl future =
                new LDAPCompareFutureResultImpl(i, request, resultHandler,
                        intermediateResponseHandler, this);
        final LDAPCompareFutureResultImpl future = new LDAPCompareFutureResultImpl(i, request,
                intermediateResponseHandler, this);
        serverConnection.handleCompare(i, request, future, future);
        return future;
    }
@@ -188,12 +178,9 @@
     */
    @Override
    public FutureResult<Result> deleteAsync(final DeleteRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int i = messageID.getAndIncrement();
        final LDAPFutureResultImpl future =
                new LDAPFutureResultImpl(i, request, resultHandler, intermediateResponseHandler,
                        this);
        final LDAPFutureResultImpl future = new LDAPFutureResultImpl(i, request, intermediateResponseHandler, this);
        serverConnection.handleDelete(i, request, future, future);
        return future;
    }
@@ -202,14 +189,11 @@
     * {@inheritDoc}
     */
    @Override
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
            final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super R> resultHandler) {
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int i = messageID.getAndIncrement();
        final LDAPExtendedFutureResultImpl<R> future =
                new LDAPExtendedFutureResultImpl<R>(i, request, resultHandler,
                        intermediateResponseHandler, this);
        final LDAPExtendedFutureResultImpl<R> future = new LDAPExtendedFutureResultImpl<R>(i, request,
                intermediateResponseHandler, this);
        serverConnection.handleExtendedRequest(i, request, future, future);
        return future;
    }
@@ -237,12 +221,9 @@
     */
    @Override
    public FutureResult<Result> modifyAsync(final ModifyRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int i = messageID.getAndIncrement();
        final LDAPFutureResultImpl future =
                new LDAPFutureResultImpl(i, request, resultHandler, intermediateResponseHandler,
                        this);
        final LDAPFutureResultImpl future = new LDAPFutureResultImpl(i, request, intermediateResponseHandler, this);
        serverConnection.handleModify(i, request, future, future);
        return future;
    }
@@ -252,12 +233,9 @@
     */
    @Override
    public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int i = messageID.getAndIncrement();
        final LDAPFutureResultImpl future =
                new LDAPFutureResultImpl(i, request, resultHandler, intermediateResponseHandler,
                        this);
        final LDAPFutureResultImpl future = new LDAPFutureResultImpl(i, request, intermediateResponseHandler, this);
        serverConnection.handleModifyDN(i, request, future, future);
        return future;
    }
@@ -276,19 +254,18 @@
     */
    @Override
    public FutureResult<Result> searchAsync(final SearchRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final SearchResultHandler resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
        final int i = messageID.getAndIncrement();
        final LDAPSearchFutureResultImpl future =
                new LDAPSearchFutureResultImpl(i, request, resultHandler,
                        intermediateResponseHandler, this);
        serverConnection.handleSearch(i, request, future, future);
        final LDAPSearchFutureResultImpl future = new LDAPSearchFutureResultImpl(i, request, entryHandler,
                intermediateResponseHandler, this);
        serverConnection.handleSearch(i, request, future, future, future);
        return future;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("InternalConnection(");
opendj-core/src/main/java/org/forgerock/opendj/ldap/InternalConnectionFactory.java
@@ -22,12 +22,16 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import com.forgerock.opendj.util.CompletedFutureResult;
import org.forgerock.util.promise.Promise;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
import static org.forgerock.util.promise.Promises.*;
/**
 * A special {@code ConnectionFactory} which waits for internal connection
@@ -63,30 +67,25 @@
        // Nothing to do.
    }
    @Override
    public Connection getConnection() throws ErrorResultException {
        final ServerConnection<Integer> serverConnection = factory.handleAccept(clientContext);
        return new InternalConnection(serverConnection);
    }
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> handler) {
    @Override
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        final ServerConnection<Integer> serverConnection;
        try {
            serverConnection = factory.handleAccept(clientContext);
        } catch (final ErrorResultException e) {
            if (handler != null) {
                handler.handleErrorResult(e);
            }
            return new CompletedFutureResult<Connection>(e);
            return newFailedFutureResult(e);
        }
        final InternalConnection connection = new InternalConnection(serverConnection);
        if (handler != null) {
            handler.handleResult(connection);
        }
        return new CompletedFutureResult<Connection>(connection);
        return newSuccessfulPromise((Connection) new InternalConnection(serverConnection));
    }
    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("InternalConnectionFactory(");
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
@@ -32,6 +32,7 @@
import org.forgerock.opendj.ldap.spi.LDAPConnectionFactoryImpl;
import org.forgerock.opendj.ldap.spi.TransportProvider;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Promise;
/**
 * A factory class which can be used to obtain connections to an LDAP Directory
@@ -47,7 +48,7 @@
    /*
     * Transport provider that provides the implementation of this factory.
     */
    private TransportProvider provider;
    private final TransportProvider provider;
    /**
     * Creates a new LDAP connection factory which can be used to create LDAP
@@ -96,9 +97,8 @@
    }
    @Override
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> handler) {
        return impl.getConnectionAsync(handler);
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        return impl.getConnectionAsync();
    }
    @Override
opendj-core/src/main/java/org/forgerock/opendj/ldap/LoadBalancer.java
@@ -22,13 +22,15 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import com.forgerock.opendj.util.CompletedFutureResult;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Promise;
import static org.forgerock.util.promise.Promises.*;
/**
 * A load balancing connection factory allocates connections using the provided
@@ -54,20 +56,16 @@
    }
    @Override
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> resultHandler) {
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        final ConnectionFactory factory;
        try {
            factory = algorithm.getConnectionFactory();
        } catch (final ErrorResultException e) {
            if (resultHandler != null) {
                resultHandler.handleErrorResult(e);
            }
            return new CompletedFutureResult<Connection>(e);
            return newFailedPromise(e);
        }
        return factory.getConnectionAsync(resultHandler);
        return factory.getConnectionAsync();
    }
    @Override
opendj-core/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
@@ -20,7 +20,7 @@
 *
 * CDDL HEADER END
 *
 *      Copyright 2013 ForgeRock AS.
 *      Copyright 2013-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
@@ -248,7 +248,7 @@
            }
            resultHandler.handleResult(getResult(request, null, request));
        } catch (final ErrorResultException e) {
            resultHandler.handleErrorResult(e);
            resultHandler.handleError(e);
        }
    }
@@ -279,17 +279,17 @@
            }
            resultHandler.handleResult(getBindResult(request, entry, entry));
        } catch (final LocalizedIllegalArgumentException e) {
            resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR, e));
            resultHandler.handleError(newErrorResult(ResultCode.PROTOCOL_ERROR, e));
        } catch (final EntryNotFoundException e) {
            /*
             * Usually you would not include a diagnostic message, but we'll add
             * one here because the memory back-end is not intended for
             * production use.
             */
            resultHandler.handleErrorResult(newErrorResult(ResultCode.INVALID_CREDENTIALS,
            resultHandler.handleError(newErrorResult(ResultCode.INVALID_CREDENTIALS,
                    "Unknown user"));
        } catch (final ErrorResultException e) {
            resultHandler.handleErrorResult(e);
            resultHandler.handleError(e);
        }
    }
@@ -310,7 +310,7 @@
            resultHandler.handleResult(getCompareResult(request, entry, entry.containsAttribute(
                    assertion, null)));
        } catch (final ErrorResultException e) {
            resultHandler.handleErrorResult(e);
            resultHandler.handleError(e);
        }
    }
@@ -338,9 +338,9 @@
            }
            resultHandler.handleResult(getResult(request, entry, null));
        } catch (final DecodeException e) {
            resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR, e));
            resultHandler.handleError(newErrorResult(ResultCode.PROTOCOL_ERROR, e));
        } catch (final ErrorResultException e) {
            resultHandler.handleErrorResult(e);
            resultHandler.handleError(e);
        }
    }
@@ -349,7 +349,7 @@
            final RequestContext requestContext, final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<R> resultHandler) {
        resultHandler.handleErrorResult(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
        resultHandler.handleError(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
                "Extended request operation not supported"));
    }
@@ -368,7 +368,7 @@
            }
            resultHandler.handleResult(getResult(request, entry, newEntry));
        } catch (final ErrorResultException e) {
            resultHandler.handleErrorResult(e);
            resultHandler.handleError(e);
        }
    }
@@ -376,42 +376,40 @@
    public void handleModifyDN(final RequestContext requestContext, final ModifyDNRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<Result> resultHandler) {
        resultHandler.handleErrorResult(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
        resultHandler.handleError(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
                "ModifyDN request operation not supported"));
    }
    @Override
    public void handleSearch(final RequestContext requestContext, final SearchRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final SearchResultHandler resultHandler) {
        final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler,
        ResultHandler<Result> resultHandler) {
        try {
            final DN dn = request.getName();
            final SearchScope scope = request.getScope();
            final Filter filter = request.getFilter();
            final Matcher matcher = filter.matcher(schema);
            final AttributeFilter attributeFilter =
                    new AttributeFilter(request.getAttributes(), schema).typesOnly(request
                            .isTypesOnly());
                new AttributeFilter(request.getAttributes(), schema).typesOnly(request.isTypesOnly());
            if (scope.equals(SearchScope.BASE_OBJECT)) {
                final Entry baseEntry = getRequiredEntry(request, dn);
                if (matcher.matches(baseEntry).toBoolean()) {
                    sendEntry(attributeFilter, resultHandler, baseEntry);
                    sendEntry(attributeFilter, entryHandler, baseEntry);
                }
                resultHandler.handleResult(newResult(ResultCode.SUCCESS));
            } else if (scope.equals(SearchScope.SINGLE_LEVEL) || scope.equals(SearchScope.SUBORDINATES)
                || scope.equals(SearchScope.WHOLE_SUBTREE)) {
                searchWithSubordinates(requestContext, resultHandler, dn, matcher, attributeFilter,
                        request.getSizeLimit(), scope, request.getControl(
                                SimplePagedResultsControl.DECODER, new DecodeOptions()));
                searchWithSubordinates(requestContext, entryHandler, resultHandler, dn, matcher, attributeFilter,
                    request.getSizeLimit(), scope,
                    request.getControl(SimplePagedResultsControl.DECODER, new DecodeOptions()));
            } else {
                throw newErrorResult(ResultCode.PROTOCOL_ERROR,
                        "Search request contains an unsupported search scope");
                throw newErrorResult(ResultCode.PROTOCOL_ERROR, "Search request contains an unsupported search scope");
            }
        } catch (DecodeException e) {
            resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
                    ResultCode.PROTOCOL_ERROR, e.getMessage(), e));
            resultHandler.handleError(ErrorResultException.newErrorResult(ResultCode.PROTOCOL_ERROR, e.getMessage(),
                e));
        } catch (final ErrorResultException e) {
            resultHandler.handleErrorResult(e);
            resultHandler.handleError(e);
        }
    }
@@ -490,11 +488,10 @@
     * @throws ErrorResultException
     *           If the request is unsuccessful.
     */
    private void searchWithSubordinates(final RequestContext requestContext,
            final SearchResultHandler resultHandler, final DN dn, final Matcher matcher,
    private void searchWithSubordinates(final RequestContext requestContext, final SearchResultHandler entryHandler,
            final ResultHandler<Result> resultHandler, final DN dn, final Matcher matcher,
            final AttributeFilter attributeFilter, final int sizeLimit, SearchScope scope,
            SimplePagedResultsControl pagedResults) throws CancelledResultException,
            ErrorResultException {
            SimplePagedResultsControl pagedResults) throws CancelledResultException, ErrorResultException {
        final int pageSize = pagedResults != null ? pagedResults.getSize() : 0;
        final int offset = (pagedResults != null && !pagedResults.getCookie().isEmpty())
                ? Integer.valueOf(pagedResults.getCookie().toString()) : 0;
@@ -504,7 +501,7 @@
        for (final Entry entry : subtree.values()) {
            requestContext.checkIfCancelled(false);
            if (scope.equals(SearchScope.WHOLE_SUBTREE) || entry.getName().isChildOf(dn)
                || (scope.equals(SearchScope.SUBORDINATES) && !entry.getName().equals(dn))) {
                    || (scope.equals(SearchScope.SUBORDINATES) && !entry.getName().equals(dn))) {
                if (matcher.matches(entry).toBoolean()) {
                    /*
                     * This entry is going to be returned to the client so it
@@ -522,7 +519,7 @@
                    }
                    // Send the entry back to the client.
                    if (!sendEntry(attributeFilter, resultHandler, entry)) {
                    if (!sendEntry(attributeFilter, entryHandler, entry)) {
                        // Client has disconnected or cancelled.
                        break;
                    }
@@ -538,9 +535,8 @@
        }
        final Result result = newResult(ResultCode.SUCCESS);
        if (pageSize > 0) {
            final ByteString cookie =
                    numberOfResults == pageSize ? ByteString.valueOf(String.valueOf(position))
                            : ByteString.empty();
            final ByteString cookie = numberOfResults == pageSize ? ByteString.valueOf(String.valueOf(position))
                    : ByteString.empty();
            result.addControl(SimplePagedResultsControl.newControl(true, 0, cookie));
        }
        resultHandler.handleResult(result);
opendj-core/src/main/java/org/forgerock/opendj/ldap/RequestHandler.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
@@ -213,13 +213,16 @@
     * @param intermediateResponseHandler
     *            The handler which should be used to send back any intermediate
     *            responses to the client.
     * @param entryHandler
     *            The entry handler which should be used to send back the search
     *            entries results to the client.
     * @param resultHandler
     *            The handler which should be used to send back the search
     *            results to the client.
     *            The handler which should be used to send back the result to
     *            the client.
     * @throws UnsupportedOperationException
     *             If this request handler does not handle search requests.
     */
    void handleSearch(C requestContext, SearchRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            SearchResultHandler resultHandler);
        IntermediateResponseHandler intermediateResponseHandler, SearchResultHandler entryHandler,
        ResultHandler<Result> resultHandler);
}
opendj-core/src/main/java/org/forgerock/opendj/ldap/RequestHandlerFactoryAdapter.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2011-2013 ForgeRock AS
 *      Copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
@@ -103,7 +103,7 @@
                final R cancelResult =
                        request.getResultDecoder().newExtendedErrorResult(ResultCode.TOO_LATE, "",
                                "");
                resultHandler.handleErrorResult(ErrorResultException.newErrorResult(cancelResult));
                resultHandler.handleError(ErrorResultException.newErrorResult(cancelResult));
            }
        }
@@ -238,7 +238,7 @@
         * {@inheritDoc}
         */
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            if (clientConnection.removePendingRequest(this)) {
                if (setResult(error.getResult())) {
                    /*
@@ -248,7 +248,7 @@
                     * not be sent to the client.
                     */
                }
                resultHandler.handleErrorResult(error);
                resultHandler.handleError(error);
            }
        }
@@ -293,7 +293,7 @@
                if (cancelResultHandler != null) {
                    final Result result =
                            Responses.newGenericExtendedResult(ResultCode.CANNOT_CANCEL);
                    cancelResultHandler.handleErrorResult(newErrorResult(result));
                    cancelResultHandler.handleError(newErrorResult(result));
                }
                return;
            }
@@ -370,7 +370,7 @@
                    cancelResultHandler.handleResult(result);
                } else {
                    final Result result = Responses.newGenericExtendedResult(ResultCode.TOO_LATE);
                    cancelResultHandler.handleErrorResult(ErrorResultException
                    cancelResultHandler.handleError(ErrorResultException
                            .newErrorResult(result));
                }
            }
@@ -445,13 +445,16 @@
    /**
     * Search request context implementation.
     */
    private final static class SearchRequestContextImpl extends
            RequestContextImpl<Result, SearchResultHandler> implements SearchResultHandler {
    private final static class SearchRequestContextImpl extends RequestContextImpl<Result, ResultHandler<Result>>
        implements SearchResultHandler {
        private final SearchResultHandler entryHandler;
        private SearchRequestContextImpl(final ServerConnectionImpl clientConnection,
                final SearchResultHandler resultHandler, final int messageID,
                final boolean isCancelSupported) {
            final SearchResultHandler entryHandler, final ResultHandler<Result> resultHandler, final int messageID,
            final boolean isCancelSupported) {
            super(clientConnection, resultHandler, messageID, isCancelSupported);
            this.entryHandler = entryHandler;
        }
        /**
@@ -459,7 +462,7 @@
         */
        @Override
        public boolean handleEntry(final SearchResultEntry entry) {
            return resultHandler.handleEntry(entry);
            return entryHandler.handleEntry(entry);
        }
        /**
@@ -467,7 +470,7 @@
         */
        @Override
        public boolean handleReference(final SearchResultReference reference) {
            return resultHandler.handleReference(reference);
            return entryHandler.handleReference(reference);
        }
    }
@@ -604,7 +607,7 @@
                                    new DecodeOptions());
                } catch (final DecodeException e) {
                    // Couldn't decode a cancel request.
                    resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR, e
                    resultHandler.handleError(newErrorResult(ResultCode.PROTOCOL_ERROR, e
                            .getLocalizedMessage()));
                    return;
                }
@@ -630,8 +633,7 @@
                         * Couldn't find the request. Invoke on context in order
                         * to remove pending request.
                         */
                        requestContext
                                .handleErrorResult(newErrorResult(ResultCode.NO_SUCH_OPERATION));
                        requestContext.handleError(newErrorResult(ResultCode.NO_SUCH_OPERATION));
                    }
                }
            } else {
@@ -691,13 +693,13 @@
         */
        @Override
        public void handleSearch(final Integer messageID, final SearchRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final SearchResultHandler resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler,
            final ResultHandler<Result> resultHandler) {
            final SearchRequestContextImpl requestContext =
                    new SearchRequestContextImpl(this, resultHandler, messageID, true);
                new SearchRequestContextImpl(this, entryHandler, resultHandler, messageID, true);
            if (addPendingRequest(requestContext)) {
                requestHandler.handleSearch(requestContext, request, intermediateResponseHandler,
                        requestContext);
                requestHandler.handleSearch(requestContext, request, intermediateResponseHandler, entryHandler,
                    requestContext);
            }
        }
@@ -706,14 +708,13 @@
            if (isClosed.get()) {
                final LocalizableMessage message = INFO_CLIENT_CONNECTION_CLOSING.get();
                requestContext.handleErrorResult(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
                requestContext.handleError(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
                        message.toString()));
                return false;
            } else if (pendingRequests.putIfAbsent(messageID, requestContext) != null) {
                final LocalizableMessage message =
                        WARN_CLIENT_DUPLICATE_MESSAGE_ID.get(requestContext.getMessageID());
                requestContext.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR, message
                        .toString()));
                requestContext.handleError(newErrorResult(ResultCode.PROTOCOL_ERROR, message.toString()));
                return false;
            } else if (isClosed.get()) {
                /*
@@ -723,8 +724,7 @@
                pendingRequests.remove(messageID);
                final LocalizableMessage message = INFO_CLIENT_CONNECTION_CLOSING.get();
                requestContext.handleErrorResult(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
                        message.toString()));
                requestContext.handleError(newErrorResult(ResultCode.UNWILLING_TO_PERFORM, message.toString()));
                return false;
            } else {
                /*
opendj-core/src/main/java/org/forgerock/opendj/ldap/ResultHandler.java
@@ -22,11 +22,14 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.SuccessHandler;
/**
 * A completion handler for consuming the result of an asynchronous operation or
 * connection attempts.
@@ -46,7 +49,7 @@
 * @param <S>
 *            The type of result handled by this result handler.
 */
public interface ResultHandler<S> {
public interface ResultHandler<S> extends SuccessHandler<S>, FailureHandler<ErrorResultException> {
    /**
     * Invoked when the asynchronous operation has failed.
     *
@@ -54,7 +57,7 @@
     *            The error result exception indicating why the asynchronous
     *            operation has failed.
     */
    void handleErrorResult(ErrorResultException error);
    void handleError(ErrorResultException error);
    /**
     * Invoked when the asynchronous operation has completed successfully.
opendj-core/src/main/java/org/forgerock/opendj/ldap/RootDSE.java
@@ -36,8 +36,9 @@
import org.forgerock.opendj.ldap.schema.CoreSchema;
import com.forgerock.opendj.util.Collections2;
import com.forgerock.opendj.util.FutureResultTransformer;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Function;
/**
 * The root DSE is a DSA-specific Entry (DSE) and not part of any naming context
@@ -151,21 +152,13 @@
     */
    public static FutureResult<RootDSE> readRootDSEAsync(final Connection connection,
            final ResultHandler<? super RootDSE> handler) {
        final FutureResultTransformer<SearchResultEntry, RootDSE> future =
                new FutureResultTransformer<SearchResultEntry, RootDSE>(handler) {
                    @Override
                    protected RootDSE transformResult(final SearchResultEntry result)
                            throws ErrorResultException {
                        return valueOf(result);
                    }
                };
        final FutureResult<SearchResultEntry> innerFuture =
                connection.searchSingleEntryAsync(SEARCH_REQUEST, future);
        future.setFutureResult(innerFuture);
        return future;
        return FutureResultWrapper.asFutureResult(connection.searchSingleEntryAsync(SEARCH_REQUEST).then(
            new Function<SearchResultEntry, RootDSE, ErrorResultException>() {
                @Override
                public RootDSE apply(SearchResultEntry result) {
                    return valueOf(result);
                }
            }));
    }
    /**
@@ -422,7 +415,7 @@
    private <N> Collection<N> getMultiValuedAttribute(
            final AttributeDescription attributeDescription,
            final Function<ByteString, N, Void> function) {
        final org.forgerock.opendj.ldap.Function<ByteString, N, Void> function) {
        // The returned collection is unmodifiable because we may need to
        // return an empty collection if the attribute does not exist in the
        // underlying entry. If a value is then added to the returned empty
@@ -438,7 +431,7 @@
    }
    private <N> N getSingleValuedAttribute(final AttributeDescription attributeDescription,
            final Function<ByteString, N, Void> function) {
        final org.forgerock.opendj.ldap.Function<ByteString, N, Void> function) {
        final Attribute attr = entry.getAttribute(attributeDescription);
        if (attr == null || attr.isEmpty()) {
            return null;
opendj-core/src/main/java/org/forgerock/opendj/ldap/SearchResultHandler.java
@@ -22,12 +22,11 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
@@ -45,7 +44,7 @@
 * avoid keeping the invoking thread from dispatching to other completion
 * handlers.
 */
public interface SearchResultHandler extends ResultHandler<Result> {
public interface SearchResultHandler {
    /**
     * Invoked each time a search result entry is returned from an asynchronous
     * search operation.
opendj-core/src/main/java/org/forgerock/opendj/ldap/responses/AbstractExtendedResultDecoder.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 *      Portions copyright 2012-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.responses;
@@ -68,13 +68,13 @@
        return new ResultHandler<S>() {
            @Override
            public void handleErrorResult(final ErrorResultException error) {
            public void handleError(final ErrorResultException error) {
                final Result result = error.getResult();
                final R adaptedResult =
                        request.getResultDecoder().newExtendedErrorResult(result.getResultCode(),
                                result.getMatchedDN(), result.getDiagnosticMessage());
                adaptedResult.setCause(result.getCause());
                resultHandler.handleErrorResult(newErrorResult(adaptedResult));
                resultHandler.handleError(newErrorResult(adaptedResult));
            }
            @Override
@@ -85,7 +85,7 @@
                    resultHandler.handleResult(adaptedResult);
                } catch (final DecodeException e) {
                    final R adaptedResult = request.getResultDecoder().adaptDecodeException(e);
                    resultHandler.handleErrorResult(newErrorResult(adaptedResult));
                    resultHandler.handleError(newErrorResult(adaptedResult));
                }
            }
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
@@ -27,9 +27,6 @@
 */
package org.forgerock.opendj.ldap.schema;
import static org.forgerock.opendj.ldap.AttributeDescription.objectClass;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
@@ -51,11 +48,15 @@
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.RDN;
import org.forgerock.opendj.ldap.ResultHandler;
import com.forgerock.opendj.util.FutureResultTransformer;
import com.forgerock.opendj.util.StaticUtils;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Function;
import com.forgerock.opendj.util.StaticUtils;
import static org.forgerock.opendj.ldap.AttributeDescription.*;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
import static com.forgerock.opendj.ldap.CoreMessages.*;
/**
 * This class defines a data structure that holds information about the
@@ -996,8 +997,6 @@
     *            read.
     * @param name
     *            The distinguished name of the subschema sub-entry.
     * @param handler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the retrieved schema.
     * @throws UnsupportedOperationException
@@ -1008,24 +1007,15 @@
     * @throws NullPointerException
     *             If the {@code connection} or {@code name} was {@code null}.
     */
    public static FutureResult<Schema> readSchemaAsync(final Connection connection, final DN name,
            final ResultHandler<? super Schema> handler) {
        final FutureResultTransformer<SchemaBuilder, Schema> future =
                new FutureResultTransformer<SchemaBuilder, Schema>(handler) {
    public static FutureResult<Schema> readSchemaAsync(final Connection connection, final DN name) {
        final SchemaBuilder builder = new SchemaBuilder();
        return asFutureResult(builder.addSchemaAsync(connection, name, true).then(
                new Function<SchemaBuilder, Schema, ErrorResultException>() {
                    @Override
                    protected Schema transformResult(final SchemaBuilder builder)
                            throws ErrorResultException {
                    public Schema apply(SchemaBuilder builder) throws ErrorResultException {
                        return builder.toSchema();
                    }
                };
        final SchemaBuilder builder = new SchemaBuilder();
        final FutureResult<SchemaBuilder> innerFuture =
                builder.addSchemaAsync(connection, name, future, true);
        future.setFutureResult(innerFuture);
        return future;
                }));
    }
    /**
@@ -1085,9 +1075,6 @@
     * @param name
     *            The distinguished name of the entry whose schema is to be
     *            located.
     * @param handler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @return A future representing the retrieved schema.
     * @throws UnsupportedOperationException
     *             If the connection does not support search operations.
@@ -1097,25 +1084,16 @@
     * @throws NullPointerException
     *             If the {@code connection} or {@code name} was {@code null}.
     */
    public static FutureResult<Schema> readSchemaForEntryAsync(final Connection connection,
            final DN name, final ResultHandler<? super Schema> handler) {
        final FutureResultTransformer<SchemaBuilder, Schema> future =
                new FutureResultTransformer<SchemaBuilder, Schema>(handler) {
                    @Override
                    protected Schema transformResult(final SchemaBuilder builder)
                            throws ErrorResultException {
                        return builder.toSchema();
                    }
                };
    public static FutureResult<Schema> readSchemaForEntryAsync(final Connection connection, final DN name) {
        final SchemaBuilder builder = new SchemaBuilder();
        final FutureResult<SchemaBuilder> innerFuture =
                builder.addSchemaForEntryAsync(connection, name, future, true);
        future.setFutureResult(innerFuture);
        return future;
        return asFutureResult(builder.addSchemaForEntryAsync(connection, name, true).then(
            new Function<SchemaBuilder, Schema, ErrorResultException>() {
                @Override
                public Schema apply(SchemaBuilder builder) throws ErrorResultException {
                    return builder.toSchema();
                }
            }));
    }
    /**
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
@@ -28,18 +28,6 @@
package org.forgerock.opendj.ldap.schema;
import static com.forgerock.opendj.util.StaticUtils.toLowerCase;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.schema.Schema.*;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.OMR_GENERIC_ENUM_NAME;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.SCHEMA_PROPERTY_APPROX_RULE;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.unmodifiableCopyOfExtraProperties;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.unmodifiableCopyOfList;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.unmodifiableCopyOfSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -66,17 +54,26 @@
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.Function;
import org.forgerock.util.promise.Promise;
import com.forgerock.opendj.util.FutureResultTransformer;
import com.forgerock.opendj.util.RecursiveFutureResult;
import com.forgerock.opendj.util.StaticUtils;
import com.forgerock.opendj.util.SubstringReader;
import org.forgerock.util.Reject;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
import static org.forgerock.opendj.ldap.schema.Schema.*;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.*;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.*;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import static com.forgerock.opendj.util.StaticUtils.*;
/**
 * Schema builders should be used for incremental construction of new schemas.
@@ -94,15 +91,19 @@
    private static final String[] SUBSCHEMA_SUBENTRY_ATTRS =
            new String[] { ATTR_SUBSCHEMA_SUBENTRY };
    // Constructs a search request for retrieving the subschemaSubentry
    // attribute from the named entry.
    /**
     * Constructs a search request for retrieving the subschemaSubentry
     * attribute from the named entry.
     */
    private static SearchRequest getReadSchemaForEntrySearchRequest(final DN dn) {
        return Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT, Filter.objectClassPresent(),
                SUBSCHEMA_SUBENTRY_ATTRS);
    }
    // Constructs a search request for retrieving the named subschema
    // sub-entry.
    /**
     * Constructs a search request for retrieving the named subschema
     * sub-entry.
     */
    private static SearchRequest getReadSchemaSearchRequest(final DN dn) {
        return Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT, SUBSCHEMA_FILTER,
                SUBSCHEMA_ATTRS);
@@ -158,11 +159,13 @@
    private String defaultSyntaxOID;
    private String defaultMatchingRuleOID;
    // A schema which should be copied into this builder on any mutation.
    private Schema copyOnWriteSchema = null;
    /** A schema which should be copied into this builder on any mutation. */
    private Schema copyOnWriteSchema;
    // A unique ID which can be used to uniquely identify schemas
    // constructed without a name.
    /**
     * A unique ID which can be used to uniquely identify schemas
     * constructed without a name.
     */
    private static final AtomicInteger NEXT_SCHEMA_ID = new AtomicInteger();
    /**
@@ -256,9 +259,8 @@
            // then that is an error.
            final char c = reader.read();
            if (c != '(') {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(definition, (reader
                                .pos() - 1), String.valueOf(c));
                final LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(
                    definition, reader.pos() - 1, String.valueOf(c));
                throw new LocalizedIllegalArgumentException(message);
            }
@@ -298,39 +300,39 @@
                if (tokenName == null) {
                    // No more tokens.
                    break;
                } else if (tokenName.equalsIgnoreCase("name")) {
                } else if ("name".equalsIgnoreCase(tokenName)) {
                    names = SchemaUtils.readNameDescriptors(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("desc")) {
                } else if ("desc".equalsIgnoreCase(tokenName)) {
                    // This specifies the description for the attribute type. It
                    // is an arbitrary string of characters enclosed in single
                    // quotes.
                    description = SchemaUtils.readQuotedString(reader);
                } else if (tokenName.equalsIgnoreCase("obsolete")) {
                } else if ("obsolete".equalsIgnoreCase(tokenName)) {
                    // This indicates whether the attribute type should be
                    // considered obsolete. We do not need to do any more
                    // parsing for this token.
                    isObsolete = true;
                } else if (tokenName.equalsIgnoreCase("sup")) {
                } else if ("sup".equalsIgnoreCase(tokenName)) {
                    // This specifies the name or OID of the superior attribute
                    // type from which this attribute type should inherit its
                    // properties.
                    superiorType = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("equality")) {
                } else if ("equality".equalsIgnoreCase(tokenName)) {
                    // This specifies the name or OID of the equality matching
                    // rule to use for this attribute type.
                    equalityMatchingRule =
                            SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("ordering")) {
                } else if ("ordering".equalsIgnoreCase(tokenName)) {
                    // This specifies the name or OID of the ordering matching
                    // rule to use for this attribute type.
                    orderingMatchingRule =
                            SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("substr")) {
                } else if ("substr".equalsIgnoreCase(tokenName)) {
                    // This specifies the name or OID of the substring matching
                    // rule to use for this attribute type.
                    substringMatchingRule =
                            SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("syntax")) {
                } else if ("syntax".equalsIgnoreCase(tokenName)) {
                    // This specifies the numeric OID of the syntax for this
                    // matching rule. It may optionally be immediately followed
                    // by an open curly brace, an integer definition, and a close
@@ -340,28 +342,28 @@
                    // does not impose any practical limit on the length of attribute
                    // values.
                    syntax = SchemaUtils.readOIDLen(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("single-definition")) {
                } else if ("single-definition".equalsIgnoreCase(tokenName)) {
                    // This indicates that attributes of this type are allowed
                    // to have at most one definition. We do not need any more
                    // parsing for this token.
                    isSingleValue = true;
                } else if (tokenName.equalsIgnoreCase("single-value")) {
                } else if ("single-value".equalsIgnoreCase(tokenName)) {
                    // This indicates that attributes of this type are allowed
                    // to have at most one value. We do not need any more parsing
                    // for this token.
                    isSingleValue = true;
                } else if (tokenName.equalsIgnoreCase("collective")) {
                } else if ("collective".equalsIgnoreCase(tokenName)) {
                    // This indicates that attributes of this type are
                    // collective
                    // (i.e., have their values generated dynamically in some
                    // way). We do not need any more parsing for this token.
                    isCollective = true;
                } else if (tokenName.equalsIgnoreCase("no-user-modification")) {
                } else if ("no-user-modification".equalsIgnoreCase(tokenName)) {
                    // This indicates that the values of attributes of this type
                    // are not to be modified by end users. We do not need any
                    // more parsing for this token.
                    isNoUserModification = true;
                } else if (tokenName.equalsIgnoreCase("usage")) {
                } else if ("usage".equalsIgnoreCase(tokenName)) {
                    // This specifies the usage string for this attribute type.
                    // It should be followed by one of the strings
                    // "userApplications", "directoryOperation",
@@ -377,18 +379,17 @@
                    reader.reset();
                    final String usageStr = reader.read(length);
                    if (usageStr.equalsIgnoreCase("userapplications")) {
                    if ("userapplications".equalsIgnoreCase(usageStr)) {
                        attributeUsage = AttributeUsage.USER_APPLICATIONS;
                    } else if (usageStr.equalsIgnoreCase("directoryoperation")) {
                    } else if ("directoryoperation".equalsIgnoreCase(usageStr)) {
                        attributeUsage = AttributeUsage.DIRECTORY_OPERATION;
                    } else if (usageStr.equalsIgnoreCase("distributedoperation")) {
                    } else if ("distributedoperation".equalsIgnoreCase(usageStr)) {
                        attributeUsage = AttributeUsage.DISTRIBUTED_OPERATION;
                    } else if (usageStr.equalsIgnoreCase("dsaoperation")) {
                    } else if ("dsaoperation".equalsIgnoreCase(usageStr)) {
                        attributeUsage = AttributeUsage.DSA_OPERATION;
                    } else {
                        final LocalizableMessage message =
                                WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE1.get(definition,
                                        usageStr);
                            WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE1.get(definition, usageStr);
                        throw new LocalizedIllegalArgumentException(message);
                    }
                } else if (tokenName.matches("^X-[A-Za-z_-]+$")) {
@@ -556,9 +557,8 @@
            // then that is an error.
            final char c = reader.read();
            if (c != '(') {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS.get(definition,
                                (reader.pos() - 1), String.valueOf(c));
                final LocalizableMessage message = ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS.get(
                    definition, reader.pos() - 1, String.valueOf(c));
                throw new LocalizedIllegalArgumentException(message);
            }
@@ -593,27 +593,27 @@
                if (tokenName == null) {
                    // No more tokens.
                    break;
                } else if (tokenName.equalsIgnoreCase("name")) {
                } else if ("name".equalsIgnoreCase(tokenName)) {
                    names = SchemaUtils.readNameDescriptors(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("desc")) {
                } else if ("desc".equalsIgnoreCase(tokenName)) {
                    // This specifies the description for the attribute type. It
                    // is an arbitrary string of characters enclosed in single
                    // quotes.
                    description = SchemaUtils.readQuotedString(reader);
                } else if (tokenName.equalsIgnoreCase("obsolete")) {
                } else if ("obsolete".equalsIgnoreCase(tokenName)) {
                    // This indicates whether the attribute type should be
                    // considered obsolete. We do not need to do any more
                    // parsing for this token.
                    isObsolete = true;
                } else if (tokenName.equalsIgnoreCase("aux")) {
                } else if ("aux".equalsIgnoreCase(tokenName)) {
                    auxiliaryClasses = SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("must")) {
                } else if ("must".equalsIgnoreCase(tokenName)) {
                    requiredAttributes =
                            SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("may")) {
                } else if ("may".equalsIgnoreCase(tokenName)) {
                    optionalAttributes =
                            SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("not")) {
                } else if ("not".equalsIgnoreCase(tokenName)) {
                    prohibitedAttributes =
                            SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.matches("^X-[A-Za-z_-]+$")) {
@@ -786,9 +786,8 @@
            // then that is an error.
            final char c = reader.read();
            if (c != '(') {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get(definition,
                                (reader.pos() - 1), String.valueOf(c));
                final LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get(
                    definition, reader.pos() - 1, String.valueOf(c));
                throw new LocalizedIllegalArgumentException(message);
            }
@@ -820,21 +819,21 @@
                if (tokenName == null) {
                    // No more tokens.
                    break;
                } else if (tokenName.equalsIgnoreCase("name")) {
                } else if ("name".equalsIgnoreCase(tokenName)) {
                    names = SchemaUtils.readNameDescriptors(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("desc")) {
                } else if ("desc".equalsIgnoreCase(tokenName)) {
                    // This specifies the description for the attribute type. It
                    // is an arbitrary string of characters enclosed in single
                    // quotes.
                    description = SchemaUtils.readQuotedString(reader);
                } else if (tokenName.equalsIgnoreCase("obsolete")) {
                } else if ("obsolete".equalsIgnoreCase(tokenName)) {
                    // This indicates whether the attribute type should be
                    // considered obsolete. We do not need to do any more
                    // parsing for this token.
                    isObsolete = true;
                } else if (tokenName.equalsIgnoreCase("form")) {
                } else if ("form".equalsIgnoreCase(tokenName)) {
                    nameForm = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("sup")) {
                } else if ("sup".equalsIgnoreCase(tokenName)) {
                    superiorRules = SchemaUtils.readRuleIDs(reader);
                } else if (tokenName.matches("^X-[A-Za-z_-]+$")) {
                    // This must be a non-standard property and it must be
@@ -959,9 +958,8 @@
            // then that is an error.
            final char c = reader.read();
            if (c != '(') {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS.get(definition,
                                (reader.pos() - 1), String.valueOf(c));
                final LocalizableMessage message = ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS.get(
                    definition, reader.pos() - 1, String.valueOf(c));
                throw new LocalizedIllegalArgumentException(message);
            }
@@ -990,19 +988,19 @@
                if (tokenName == null) {
                    // No more tokens.
                    break;
                } else if (tokenName.equalsIgnoreCase("name")) {
                } else if ("name".equalsIgnoreCase(tokenName)) {
                    matchingRuleBuilder.names(SchemaUtils.readNameDescriptors(reader, allowMalformedNamesAndOptions));
                } else if (tokenName.equalsIgnoreCase("desc")) {
                } else if ("desc".equalsIgnoreCase(tokenName)) {
                    // This specifies the description for the matching rule. It
                    // is an arbitrary string of characters enclosed in single
                    // quotes.
                    matchingRuleBuilder.description(SchemaUtils.readQuotedString(reader));
                } else if (tokenName.equalsIgnoreCase("obsolete")) {
                } else if ("obsolete".equalsIgnoreCase(tokenName)) {
                    // This indicates whether the matching rule should be
                    // considered obsolete. We do not need to do any more
                    // parsing for this token.
                    matchingRuleBuilder.obsolete(true);
                } else if (tokenName.equalsIgnoreCase("syntax")) {
                } else if ("syntax".equalsIgnoreCase(tokenName)) {
                    syntax = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
                    matchingRuleBuilder.syntaxOID(syntax);
                } else if (tokenName.matches("^X-[A-Za-z_-]+$")) {
@@ -1081,9 +1079,8 @@
            // then that is an error.
            final char c = reader.read();
            if (c != '(') {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS.get(definition, (reader
                                .pos() - 1), String.valueOf(c));
                final LocalizableMessage message = ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS.get(
                    definition, reader.pos() - 1, String.valueOf(c));
                throw new LocalizedIllegalArgumentException(message);
            }
@@ -1114,19 +1111,19 @@
                if (tokenName == null) {
                    // No more tokens.
                    break;
                } else if (tokenName.equalsIgnoreCase("name")) {
                } else if ("name".equalsIgnoreCase(tokenName)) {
                    names = SchemaUtils.readNameDescriptors(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("desc")) {
                } else if ("desc".equalsIgnoreCase(tokenName)) {
                    // This specifies the description for the attribute type. It
                    // is an arbitrary string of characters enclosed in single
                    // quotes.
                    description = SchemaUtils.readQuotedString(reader);
                } else if (tokenName.equalsIgnoreCase("obsolete")) {
                } else if ("obsolete".equalsIgnoreCase(tokenName)) {
                    // This indicates whether the attribute type should be
                    // considered obsolete. We do not need to do any more
                    // parsing for this token.
                    isObsolete = true;
                } else if (tokenName.equalsIgnoreCase("applies")) {
                } else if ("applies".equalsIgnoreCase(tokenName)) {
                    attributes = SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.matches("^X-[A-Za-z_-]+$")) {
                    // This must be a non-standard property and it must be
@@ -1264,9 +1261,8 @@
            // then that is an error.
            final char c = reader.read();
            if (c != '(') {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get(definition, (reader
                                .pos() - 1), c);
                final LocalizableMessage message = ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get(
                    definition, reader.pos() - 1, c);
                throw new LocalizedIllegalArgumentException(message);
            }
@@ -1298,27 +1294,27 @@
                if (tokenName == null) {
                    // No more tokens.
                    break;
                } else if (tokenName.equalsIgnoreCase("name")) {
                } else if ("name".equalsIgnoreCase(tokenName)) {
                    nameFormBuilder.names(SchemaUtils.readNameDescriptors(reader,
                            allowMalformedNamesAndOptions));
                } else if (tokenName.equalsIgnoreCase("desc")) {
                } else if ("desc".equalsIgnoreCase(tokenName)) {
                    // This specifies the description for the attribute type. It
                    // is an arbitrary string of characters enclosed in single
                    // quotes.
                    nameFormBuilder.description(SchemaUtils.readQuotedString(reader));
                } else if (tokenName.equalsIgnoreCase("obsolete")) {
                } else if ("obsolete".equalsIgnoreCase(tokenName)) {
                    // This indicates whether the attribute type should be
                    // considered obsolete. We do not need to do any more
                    // parsing for this token.
                    nameFormBuilder.obsolete(true);
                } else if (tokenName.equalsIgnoreCase("oc")) {
                } else if ("oc".equalsIgnoreCase(tokenName)) {
                    structuralOID = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
                    nameFormBuilder.structuralObjectClassOID(structuralOID);
                } else if (tokenName.equalsIgnoreCase("must")) {
                } else if ("must".equalsIgnoreCase(tokenName)) {
                    requiredAttributes =
                            SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                    nameFormBuilder.requiredAttributes(requiredAttributes);
                } else if (tokenName.equalsIgnoreCase("may")) {
                } else if ("may".equalsIgnoreCase(tokenName)) {
                    nameFormBuilder.optionalAttributes(SchemaUtils.readOIDs(reader,
                            allowMalformedNamesAndOptions));
                } else if (tokenName.matches("^X-[A-Za-z_-]+$")) {
@@ -1578,9 +1574,8 @@
            // then that is an error.
            final char c = reader.read();
            if (c != '(') {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS1.get(definition,
                                (reader.pos() - 1), String.valueOf(c));
                final LocalizableMessage message =  ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS1.get(
                            definition, reader.pos() - 1, String.valueOf(c));
                throw new LocalizedIllegalArgumentException(message);
            }
@@ -1614,38 +1609,38 @@
                if (tokenName == null) {
                    // No more tokens.
                    break;
                } else if (tokenName.equalsIgnoreCase("name")) {
                } else if ("name".equalsIgnoreCase(tokenName)) {
                    names = SchemaUtils.readNameDescriptors(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("desc")) {
                } else if ("desc".equalsIgnoreCase(tokenName)) {
                    // This specifies the description for the attribute type. It
                    // is an arbitrary string of characters enclosed in single
                    // quotes.
                    description = SchemaUtils.readQuotedString(reader);
                } else if (tokenName.equalsIgnoreCase("obsolete")) {
                } else if ("obsolete".equalsIgnoreCase(tokenName)) {
                    // This indicates whether the attribute type should be
                    // considered obsolete. We do not need to do any more
                    // parsing for this token.
                    isObsolete = true;
                } else if (tokenName.equalsIgnoreCase("sup")) {
                } else if ("sup".equalsIgnoreCase(tokenName)) {
                    superiorClasses = SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("abstract")) {
                } else if ("abstract".equalsIgnoreCase(tokenName)) {
                    // This indicates that entries must not include this
                    // objectclass unless they also include a non-abstract
                    // objectclass that inherits from this class. We do not need
                    // any more parsing for this token.
                    objectClassType = ObjectClassType.ABSTRACT;
                } else if (tokenName.equalsIgnoreCase("structural")) {
                } else if ("structural".equalsIgnoreCase(tokenName)) {
                    // This indicates that this is a structural objectclass. We
                    // do not need any more parsing for this token.
                    objectClassType = ObjectClassType.STRUCTURAL;
                } else if (tokenName.equalsIgnoreCase("auxiliary")) {
                } else if ("auxiliary".equalsIgnoreCase(tokenName)) {
                    // This indicates that this is an auxiliary objectclass. We
                    // do not need any more parsing for this token.
                    objectClassType = ObjectClassType.AUXILIARY;
                } else if (tokenName.equalsIgnoreCase("must")) {
                } else if ("must".equalsIgnoreCase(tokenName)) {
                    requiredAttributes =
                            SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.equalsIgnoreCase("may")) {
                } else if ("may".equalsIgnoreCase(tokenName)) {
                    optionalAttributes =
                            SchemaUtils.readOIDs(reader, allowMalformedNamesAndOptions);
                } else if (tokenName.matches("^X-[A-Za-z_-]+$")) {
@@ -1665,7 +1660,7 @@
                }
            }
            if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID)) {
            if (EXTENSIBLE_OBJECT_OBJECTCLASS_OID.equals(oid)) {
                addObjectClass(new ObjectClass(description, extraProperties), overwrite);
            } else {
                if (objectClassType == ObjectClassType.STRUCTURAL && superiorClasses.isEmpty()) {
@@ -1726,7 +1721,7 @@
            final boolean overwrite) {
        lazyInitBuilder();
        if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID)) {
        if (EXTENSIBLE_OBJECT_OBJECTCLASS_OID.equals(oid)) {
            addObjectClass(new ObjectClass(description,
                    unmodifiableCopyOfExtraProperties(extraProperties)), overwrite);
        } else {
@@ -1958,8 +1953,6 @@
     *            read.
     * @param name
     *            The distinguished name of the subschema sub-entry.
     * @param handler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @param overwrite
     *            {@code true} if existing schema elements with the same
@@ -1974,26 +1967,18 @@
     *             If the {@code connection} or {@code name} was {@code null}.
     */
    public FutureResult<SchemaBuilder> addSchemaAsync(final Connection connection, final DN name,
            final ResultHandler<? super SchemaBuilder> handler, final boolean overwrite) {
        final boolean overwrite) {
        // The call to addSchema will perform copyOnWrite.
        final SearchRequest request = getReadSchemaSearchRequest(name);
        final FutureResultTransformer<SearchResultEntry, SchemaBuilder> future =
                new FutureResultTransformer<SearchResultEntry, SchemaBuilder>(handler) {
        return asFutureResult(connection.searchSingleEntryAsync(request).then(
                new Function<SearchResultEntry, SchemaBuilder, ErrorResultException>() {
                    @Override
                    protected SchemaBuilder transformResult(final SearchResultEntry result)
                            throws ErrorResultException {
                    public SchemaBuilder apply(SearchResultEntry result) throws ErrorResultException {
                        addSchema(result, overwrite);
                        return SchemaBuilder.this;
                    }
                };
        final FutureResult<SearchResultEntry> innerFuture =
                connection.searchSingleEntryAsync(request, future);
        future.setFutureResult(innerFuture);
        return future;
                }));
    }
    /**
@@ -2058,9 +2043,6 @@
     * @param name
     *            The distinguished name of the entry whose schema is to be
     *            located.
     * @param handler
     *            A result handler which can be used to asynchronously process
     *            the operation result when it is received, may be {@code null}.
     * @param overwrite
     *            {@code true} if existing schema elements with the same
     *            conflicting OIDs should be overwritten.
@@ -2073,29 +2055,19 @@
     * @throws NullPointerException
     *             If the {@code connection} or {@code name} was {@code null}.
     */
    public FutureResult<SchemaBuilder> addSchemaForEntryAsync(final Connection connection,
            final DN name, final ResultHandler<? super SchemaBuilder> handler,
            final boolean overwrite) {
        // The call to addSchema will perform copyOnWrite.
        final RecursiveFutureResult<SearchResultEntry, SchemaBuilder> future =
                new RecursiveFutureResult<SearchResultEntry, SchemaBuilder>(handler) {
                    @Override
                    protected FutureResult<SchemaBuilder> chainResult(
                            final SearchResultEntry innerResult,
                            final ResultHandler<? super SchemaBuilder> handler)
                            throws ErrorResultException {
                        final DN subschemaDN = getSubschemaSubentryDN(name, innerResult);
                        return addSchemaAsync(connection, subschemaDN, handler, overwrite);
                    }
                };
    public FutureResult<SchemaBuilder> addSchemaForEntryAsync(final Connection connection, final DN name,
        final boolean overwrite) {
        final SearchRequest request = getReadSchemaForEntrySearchRequest(name);
        final FutureResult<SearchResultEntry> innerFuture =
                connection.searchSingleEntryAsync(request, future);
        future.setFutureResult(innerFuture);
        return future;
        return asFutureResult(connection.searchSingleEntryAsync(request).thenAsync(
                new AsyncFunction<SearchResultEntry, SchemaBuilder, ErrorResultException>() {
                    @Override
                    public Promise<SchemaBuilder, ErrorResultException> apply(SearchResultEntry result)
                            throws ErrorResultException {
                        final DN subschemaDN = getSubschemaSubentryDN(name, result);
                        return addSchemaAsync(connection, subschemaDN, overwrite);
                    }
                }));
    }
    /**
@@ -2171,9 +2143,8 @@
            // then that is an error.
            final char c = reader.read();
            if (c != '(') {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(definition,
                                (reader.pos() - 1), String.valueOf(c));
                final LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
                    definition, reader.pos() - 1, String.valueOf(c));
                throw new LocalizedIllegalArgumentException(message);
            }
@@ -2199,7 +2170,7 @@
                if (tokenName == null) {
                    // No more tokens.
                    break;
                } else if (tokenName.equalsIgnoreCase("desc")) {
                } else if ("desc".equalsIgnoreCase(tokenName)) {
                    // This specifies the description for the syntax. It is an
                    // arbitrary string of characters enclosed in single quotes.
                    syntaxBuilder.description(SchemaUtils.readQuotedString(reader));
@@ -2220,7 +2191,7 @@
            // See if it is a enum syntax
            for (final Map.Entry<String, List<String>> property : syntaxBuilder.getExtraProperties().entrySet()) {
                if (property.getKey().equalsIgnoreCase("x-enum")) {
                if ("x-enum".equalsIgnoreCase(property.getKey())) {
                    final EnumSyntaxImpl enumImpl = new EnumSyntaxImpl(oid, property.getValue());
                    syntaxBuilder.implementation(enumImpl);
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLDAPFutureResultImpl.java
@@ -22,21 +22,20 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResultImpl;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.responses.IntermediateResponse;
import org.forgerock.opendj.ldap.responses.Result;
import com.forgerock.opendj.util.AsynchronousFutureResult;
/**
 * Abstract future result implementation.
@@ -44,9 +43,8 @@
 * @param <S>
 *            The type of result returned by this future.
 */
public abstract class AbstractLDAPFutureResultImpl<S extends Result> extends
        AsynchronousFutureResult<S, ResultHandler<? super S>> implements
        IntermediateResponseHandler {
public abstract class AbstractLDAPFutureResultImpl<S extends Result> extends FutureResultImpl<S> implements
    IntermediateResponseHandler {
    private final Connection connection;
    private IntermediateResponseHandler intermediateResponseHandler;
    private volatile long timestamp;
@@ -56,8 +54,6 @@
     *
     * @param requestID
     *            identifier of the request
     * @param resultHandler
     *            handler that consumes the result
     * @param intermediateResponseHandler
     *            handler that consumes intermediate responses from extended
     *            operations
@@ -65,10 +61,8 @@
     *            the connection to directory server
     */
    protected AbstractLDAPFutureResultImpl(final int requestID,
            final ResultHandler<? super S> resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        super(resultHandler, requestID);
        final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) {
        super(requestID);
        this.connection = connection;
        this.intermediateResponseHandler = intermediateResponseHandler;
        this.timestamp = System.currentTimeMillis();
@@ -83,40 +77,37 @@
        // the synchronizer.
        if (!isDone()) {
            updateTimestamp();
            if (intermediateResponseHandler != null) {
                if (!intermediateResponseHandler.handleIntermediateResponse(response)) {
                    intermediateResponseHandler = null;
                }
            if (intermediateResponseHandler != null
                && !intermediateResponseHandler.handleIntermediateResponse(response)) {
                intermediateResponseHandler = null;
            }
        }
        return true;
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    protected final ErrorResultException handleCancelRequest(final boolean mayInterruptIfRunning) {
        /*
         * This will abandon the request, but will also recursively cancel this
         * future. There is no risk of an infinite loop because the state of
         * this future has already been changed.
         */
        connection.abandonAsync(Requests.newAbandonRequest(getRequestID()));
        return null;
    }
    @Override
    protected final boolean isCancelable() {
    protected final ErrorResultException tryCancel(final boolean mayInterruptIfRunning) {
        /*
         * No other operations can be performed while a bind or startTLS
         * operations is active. Therefore it is not possible to cancel bind or
         * startTLS requests, since doing so will leave the connection in a
         * state which prevents other operations from being performed.
         */
        return !isBindOrStartTLS();
        if (isBindOrStartTLS()) {
            return null;
        }
        /*
         * This will abandon the request, but will also recursively cancel this
         * future. There is no risk of an infinite loop because the state of
         * this future has already been changed.
         */
        connection.abandonAsync(Requests.newAbandonRequest(getRequestID()));
        return ErrorResultException.newErrorResult(ResultCode.CLIENT_SIDE_USER_CANCELLED);
    }
    /**
     * Returns {@code true} if this future represents the result of a bind or
     * StartTLS request. The default implementation is to return {@code false}.
@@ -128,13 +119,18 @@
        return false;
    }
    @Override
    /**
     * Appends a string representation of this future's state to the provided
     * builder.
     *
     * @param sb
     *            The string builder.
     */
    protected void toString(final StringBuilder sb) {
        sb.append(" requestID = ");
        sb.append(getRequestID());
        sb.append(" timestamp = ");
        sb.append(timestamp);
        super.toString(sb);
    }
    /**
@@ -145,8 +141,7 @@
     */
    public final void adaptErrorResult(final Result result) {
        final S errorResult =
                newErrorResult(result.getResultCode(), result.getDiagnosticMessage(), result
                        .getCause());
            newErrorResult(result.getResultCode(), result.getDiagnosticMessage(), result.getCause());
        setResultOrError(errorResult);
    }
@@ -170,8 +165,7 @@
     *            cause of the error
     * @return the error result
     */
    protected abstract S newErrorResult(ResultCode resultCode, String diagnosticMessage,
            Throwable cause);
    protected abstract S newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause);
    /**
     * Sets the result associated to this future.
@@ -181,7 +175,7 @@
     */
    public final void setResultOrError(final S result) {
        if (result.getResultCode().isExceptional()) {
            handleErrorResult(ErrorResultException.newErrorResult(result));
            handleError(ErrorResultException.newErrorResult(result));
        } else {
            handleResult(result);
        }
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPBindFutureResultImpl.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
@@ -30,7 +30,6 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.requests.BindClient;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.Responses;
@@ -48,8 +47,6 @@
     *            identifier of the request
     * @param bindClient
     *            client that binds to the server
     * @param resultHandler
     *            handler that consumes result of bind
     * @param intermediateResponseHandler
     *            handler that consumes intermediate responses from extended
     *            operations
@@ -57,10 +54,9 @@
     *            the connection to directory server
     */
    public LDAPBindFutureResultImpl(final int requestID, final BindClient bindClient,
            final ResultHandler<? super BindResult> resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        super(requestID, resultHandler, intermediateResponseHandler, connection);
        super(requestID, intermediateResponseHandler, connection);
        this.bindClient = bindClient;
    }
@@ -89,9 +85,6 @@
        return bindClient;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    protected BindResult newErrorResult(final ResultCode resultCode, final String diagnosticMessage,
            final Throwable cause) {
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPCompareFutureResultImpl.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
@@ -30,7 +30,6 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.requests.CompareRequest;
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.Responses;
@@ -48,8 +47,6 @@
     *            identifier of the request
     * @param request
     *            compare request
     * @param resultHandler
     *            handler that consumes compare result
     * @param intermediateResponseHandler
     *            handler that consumes intermediate responses from extended
     *            operations
@@ -57,10 +54,9 @@
     *            the connection to directory server
     */
    public LDAPCompareFutureResultImpl(final int requestID, final CompareRequest request,
            final ResultHandler<? super CompareResult> resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        super(requestID, resultHandler, intermediateResponseHandler, connection);
        super(requestID, intermediateResponseHandler, connection);
        this.request = request;
    }
@@ -79,9 +75,7 @@
        return request;
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    protected CompareResult newErrorResult(final ResultCode resultCode, final String diagnosticMessage,
            final Throwable cause) {
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPExtendedFutureResultImpl.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
@@ -32,7 +32,6 @@
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
@@ -54,8 +53,6 @@
     *            identifier of the request
     * @param request
     *            extended request
     * @param resultHandler
     *            handler that consumes result
     * @param intermediateResponseHandler
     *            handler that consumes intermediate responses from extended
     *            operations
@@ -63,10 +60,9 @@
     *            the connection to directory server
     */
    public LDAPExtendedFutureResultImpl(final int requestID, final ExtendedRequest<R> request,
            final ResultHandler<? super R> resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        super(requestID, resultHandler, intermediateResponseHandler, connection);
        super(requestID, intermediateResponseHandler, connection);
        this.request = request;
    }
@@ -83,7 +79,7 @@
    @Override
    public boolean isBindOrStartTLS() {
        return request.getOID().equals(StartTLSExtendedRequest.OID);
        return StartTLSExtendedRequest.OID.equals(request.getOID());
    }
    /**
@@ -110,9 +106,7 @@
        return request;
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    protected R newErrorResult(final ResultCode resultCode, final String diagnosticMessage,
            final Throwable cause) {
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPFutureResultImpl.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
@@ -30,7 +30,6 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.requests.Request;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
@@ -48,8 +47,6 @@
     *            identifier of the request
     * @param request
     *            the request sent to server
     * @param resultHandler
     *            handler that consumes the result
     * @param intermediateResponseHandler
     *            handler that consumes intermediate responses from extended
     *            operations
@@ -57,10 +54,9 @@
     *            the connection to directory server
     */
    public LDAPFutureResultImpl(final int requestID, final Request request,
            final ResultHandler<? super Result> resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        super(requestID, resultHandler, intermediateResponseHandler, connection);
        super(requestID, intermediateResponseHandler, connection);
        this.request = request;
    }
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPSearchFutureResultImpl.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
@@ -64,13 +64,13 @@
     *            the connection to directory server
     */
    public LDAPSearchFutureResultImpl(final int requestID, final SearchRequest request,
            final SearchResultHandler resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        super(requestID, resultHandler, intermediateResponseHandler, connection);
        final SearchResultHandler resultHandler, final IntermediateResponseHandler intermediateResponseHandler,
        final Connection connection) {
        super(requestID, intermediateResponseHandler, connection);
        this.request = request;
        this.searchResultHandler = resultHandler;
        this.isPersistentSearch = request.containsControl(PersistentSearchRequestControl.OID)
        this.isPersistentSearch =
            request.containsControl(PersistentSearchRequestControl.OID)
                || request.containsControl(ADNotificationRequestControl.OID);
    }
opendj-core/src/main/java/org/forgerock/opendj/ldif/ConnectionEntryReader.java
@@ -22,13 +22,11 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldif;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -39,6 +37,7 @@
import org.forgerock.opendj.ldap.ErrorResultIOException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchResultReferenceIOException;
import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -47,9 +46,10 @@
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.Reject;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
/**
 * A {@code ConnectionEntryReader} is a bridge from {@code Connection}s to
 * {@code EntryReader}s. A connection entry reader allows applications to
@@ -114,7 +114,7 @@
    /**
     * Result handler that places all responses in a queue.
     */
    private final static class BufferHandler implements SearchResultHandler {
    private final static class BufferHandler implements SearchResultHandler, ResultHandler<Result> {
        private final BlockingQueue<Response> responses;
        private volatile boolean isInterrupted = false;
@@ -137,7 +137,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            try {
                responses.put(error.getResult());
            } catch (final InterruptedException e) {
@@ -209,10 +209,11 @@
     *             If {@code connection} was {@code null}.
     */
    public ConnectionEntryReader(final Connection connection, final SearchRequest searchRequest,
            final BlockingQueue<Response> entries) {
        final BlockingQueue<Response> entries) {
        Reject.ifNull(connection);
        buffer = new BufferHandler(entries);
        future = connection.searchAsync(searchRequest, null, buffer);
        future = (FutureResult<Result>) connection.searchAsync(searchRequest, buffer)
                .onSuccess(buffer).onFailure(buffer);
    }
    /**
opendj-core/src/test/java/com/forgerock/opendj/util/FutureResultTransformerTestCase.java
File was deleted
opendj-core/src/test/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnectionTestCase.java
@@ -22,16 +22,11 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.mockito.Mockito.*;
import java.util.LinkedList;
import java.util.List;
@@ -44,18 +39,26 @@
import org.forgerock.opendj.ldap.requests.GenericExtendedRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
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.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.SuccessHandler;
import org.testng.annotations.Test;
import com.forgerock.opendj.util.CompletedFutureResult;
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.forgerock.opendj.ldap.responses.Responses.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
 * Unit test for AbstractAsynchronousConnection. The tests verify that all
@@ -73,169 +76,116 @@
            this.entries = entries;
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public FutureResult<Void> abandonAsync(AbandonRequest request) {
            if (!resultCode.isExceptional()) {
                return new CompletedFutureResult<Void>((Void) null);
                return newSuccessfulFutureResult((Void) null);
            } else {
                return new CompletedFutureResult<Void>(newErrorResult(resultCode));
                return newFailedFutureResult(newErrorResult(resultCode));
            }
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public FutureResult<Result> addAsync(AddRequest request,
                IntermediateResponseHandler intermediateResponseHandler,
                ResultHandler<? super Result> resultHandler) {
            if (!resultCode.isExceptional()) {
                return new CompletedFutureResult<Result>(Responses.newResult(resultCode));
            } else {
                return new CompletedFutureResult<Result>(newErrorResult(resultCode));
            }
                IntermediateResponseHandler intermediateResponseHandler) {
            return getFutureFromResultCode(newResult(resultCode));
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public void addConnectionEventListener(ConnectionEventListener listener) {
            // Do nothing.
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public FutureResult<BindResult> bindAsync(BindRequest request,
                IntermediateResponseHandler intermediateResponseHandler,
                ResultHandler<? super BindResult> resultHandler) {
            if (!resultCode.isExceptional()) {
                return new CompletedFutureResult<BindResult>(Responses.newBindResult(resultCode));
            } else {
                return new CompletedFutureResult<BindResult>(newErrorResult(resultCode));
            }
                IntermediateResponseHandler intermediateResponseHandler) {
            return getFutureFromResultCode(newBindResult(resultCode));
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public void close(UnbindRequest request, String reason) {
            // Do nothing.
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public FutureResult<CompareResult> compareAsync(CompareRequest request,
                IntermediateResponseHandler intermediateResponseHandler,
                ResultHandler<? super CompareResult> resultHandler) {
            if (!resultCode.isExceptional()) {
                return new CompletedFutureResult<CompareResult>(Responses
                        .newCompareResult(resultCode));
            } else {
                return new CompletedFutureResult<CompareResult>(newErrorResult(resultCode));
            }
                IntermediateResponseHandler intermediateResponseHandler) {
            return getFutureFromResultCode(newCompareResult(resultCode));
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public FutureResult<Result> deleteAsync(DeleteRequest request,
                IntermediateResponseHandler intermediateResponseHandler,
                ResultHandler<? super Result> resultHandler) {
            if (!resultCode.isExceptional()) {
                return new CompletedFutureResult<Result>(Responses.newResult(resultCode));
            } else {
                return new CompletedFutureResult<Result>(newErrorResult(resultCode));
            }
                IntermediateResponseHandler intermediateResponseHandler) {
            return getFutureFromResultCode(newResult(resultCode));
        }
        /**
         * {@inheritDoc}
         */
        public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
                ExtendedRequest<R> request,
                IntermediateResponseHandler intermediateResponseHandler,
                ResultHandler<? super R> resultHandler) {
            if (!resultCode.isExceptional()) {
                return new CompletedFutureResult<R>(request.getResultDecoder()
                        .newExtendedErrorResult(resultCode, "", ""));
            } else {
                return new CompletedFutureResult<R>(newErrorResult(resultCode));
            }
        /** {@inheritDoc} */
        @Override
        public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(ExtendedRequest<R> request,
                IntermediateResponseHandler intermediateResponseHandler) {
            return getFutureFromResultCode(request.getResultDecoder().newExtendedErrorResult(resultCode, "", ""));
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public boolean isClosed() {
            return false;
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public boolean isValid() {
            return true;
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public FutureResult<Result> modifyAsync(ModifyRequest request,
                IntermediateResponseHandler intermediateResponseHandler,
                ResultHandler<? super Result> resultHandler) {
            if (!resultCode.isExceptional()) {
                return new CompletedFutureResult<Result>(Responses.newResult(resultCode));
            } else {
                return new CompletedFutureResult<Result>(newErrorResult(resultCode));
            }
                IntermediateResponseHandler intermediateResponseHandler) {
            return getFutureFromResultCode(newResult(resultCode));
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public FutureResult<Result> modifyDNAsync(ModifyDNRequest request,
                IntermediateResponseHandler intermediateResponseHandler,
                ResultHandler<? super Result> resultHandler) {
            if (!resultCode.isExceptional()) {
                return new CompletedFutureResult<Result>(Responses.newResult(resultCode));
            } else {
                return new CompletedFutureResult<Result>(newErrorResult(resultCode));
            }
                IntermediateResponseHandler intermediateResponseHandler) {
            return getFutureFromResultCode(newResult(resultCode));
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public void removeConnectionEventListener(ConnectionEventListener listener) {
            // Do nothing.
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public FutureResult<Result> searchAsync(SearchRequest request,
                IntermediateResponseHandler intermediateResponseHandler,
                SearchResultHandler resultHandler) {
                IntermediateResponseHandler intermediateResponseHandler, SearchResultHandler entryHandler) {
            for (SearchResultEntry entry : entries) {
                resultHandler.handleEntry(entry);
                entryHandler.handleEntry(entry);
            }
            return getFutureFromResultCode(newResult(resultCode));
        }
        private <T extends Result> FutureResult<T> getFutureFromResultCode(T correctResult) {
            if (resultCode.isExceptional()) {
                ErrorResultException errorResult = newErrorResult(resultCode);
                resultHandler.handleErrorResult(errorResult);
                return new CompletedFutureResult<Result>(errorResult);
                return newFailedFutureResult(newErrorResult(resultCode));
            } else {
                Result result = Responses.newResult(resultCode);
                resultHandler.handleResult(result);
                return new CompletedFutureResult<Result>(result);
                return newSuccessfulFutureResult(correctResult);
            }
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public String toString() {
            return "MockConnection";
        }
@@ -245,14 +195,14 @@
    @Test()
    public void testAddRequestSuccess() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
        final AddRequest addRequest = Requests.newAddRequest("cn=test");
        final AddRequest addRequest = newAddRequest("cn=test");
        assertThat(mockConnection.add(addRequest).getResultCode()).isEqualTo(ResultCode.SUCCESS);
    }
    @Test()
    public void testAddRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final AddRequest addRequest = Requests.newAddRequest("cn=test");
        final AddRequest addRequest = newAddRequest("cn=test");
        try {
            mockConnection.add(addRequest);
            fail();
@@ -264,14 +214,14 @@
    @Test()
    public void testBindRequestSuccess() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
        final BindRequest bindRequest = Requests.newSimpleBindRequest();
        final BindRequest bindRequest = newSimpleBindRequest();
        assertThat(mockConnection.bind(bindRequest).getResultCode()).isEqualTo(ResultCode.SUCCESS);
    }
    @Test()
    public void testBindRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final BindRequest bindRequest = Requests.newSimpleBindRequest();
        final BindRequest bindRequest = newSimpleBindRequest();
        try {
            mockConnection.bind(bindRequest);
            fail();
@@ -283,7 +233,7 @@
    @Test()
    public void testCompareRequestSuccess() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
        final CompareRequest compareRequest = Requests.newCompareRequest("cn=test", "cn", "test");
        final CompareRequest compareRequest = newCompareRequest("cn=test", "cn", "test");
        assertThat(mockConnection.compare(compareRequest).getResultCode()).isEqualTo(
                ResultCode.SUCCESS);
    }
@@ -291,7 +241,7 @@
    @Test()
    public void testCompareRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final CompareRequest compareRequest = Requests.newCompareRequest("cn=test", "cn", "test");
        final CompareRequest compareRequest = newCompareRequest("cn=test", "cn", "test");
        try {
            mockConnection.compare(compareRequest);
            fail();
@@ -303,7 +253,7 @@
    @Test()
    public void testDeleteRequestSuccess() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
        final DeleteRequest deleteRequest = Requests.newDeleteRequest("cn=test");
        final DeleteRequest deleteRequest = newDeleteRequest("cn=test");
        assertThat(mockConnection.delete(deleteRequest).getResultCode()).isEqualTo(
                ResultCode.SUCCESS);
    }
@@ -311,7 +261,7 @@
    @Test()
    public void testDeleteRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final DeleteRequest deleteRequest = Requests.newDeleteRequest("cn=test");
        final DeleteRequest deleteRequest = newDeleteRequest("cn=test");
        try {
            mockConnection.delete(deleteRequest);
            fail();
@@ -323,7 +273,7 @@
    @Test()
    public void testExtendedRequestSuccess() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
        final GenericExtendedRequest extendedRequest = Requests.newGenericExtendedRequest("test");
        final GenericExtendedRequest extendedRequest = newGenericExtendedRequest("test");
        assertThat(mockConnection.extendedRequest(extendedRequest).getResultCode()).isEqualTo(
                ResultCode.SUCCESS);
    }
@@ -331,7 +281,7 @@
    @Test()
    public void testExtendedRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final GenericExtendedRequest extendedRequest = Requests.newGenericExtendedRequest("test");
        final GenericExtendedRequest extendedRequest = newGenericExtendedRequest("test");
        try {
            mockConnection.extendedRequest(extendedRequest);
            fail();
@@ -343,7 +293,7 @@
    @Test()
    public void testModifyRequestSuccess() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
        final ModifyRequest modifyRequest = Requests.newModifyRequest("cn=test");
        final ModifyRequest modifyRequest = newModifyRequest("cn=test");
        assertThat(mockConnection.modify(modifyRequest).getResultCode()).isEqualTo(
                ResultCode.SUCCESS);
    }
@@ -351,7 +301,7 @@
    @Test()
    public void testModifyRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final ModifyRequest modifyRequest = Requests.newModifyRequest("cn=test");
        final ModifyRequest modifyRequest = newModifyRequest("cn=test");
        try {
            mockConnection.modify(modifyRequest);
            fail();
@@ -363,7 +313,7 @@
    @Test()
    public void testModifyDNRequestSuccess() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
        final ModifyDNRequest modifyDNRequest = Requests.newModifyDNRequest("cn=test", "cn=newrdn");
        final ModifyDNRequest modifyDNRequest = newModifyDNRequest("cn=test", "cn=newrdn");
        assertThat(mockConnection.modifyDN(modifyDNRequest).getResultCode()).isEqualTo(
                ResultCode.SUCCESS);
    }
@@ -371,7 +321,7 @@
    @Test()
    public void testModifyDNRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final ModifyDNRequest modifyDNRequest = Requests.newModifyDNRequest("cn=test", "cn=newrdn");
        final ModifyDNRequest modifyDNRequest = newModifyDNRequest("cn=test", "cn=newrdn");
        try {
            mockConnection.modifyDN(modifyDNRequest);
            fail();
@@ -382,10 +332,10 @@
    @Test()
    public void testSearchRequestSuccess() throws Exception {
        final SearchResultEntry entry = Responses.newSearchResultEntry("cn=test");
        final SearchResultEntry entry = newSearchResultEntry("cn=test");
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS, entry);
        final SearchRequest searchRequest =
                Requests.newSearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
                newSearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        List<SearchResultEntry> entries = new LinkedList<SearchResultEntry>();
        assertThat(mockConnection.search(searchRequest, entries).getResultCode()).isEqualTo(
                ResultCode.SUCCESS);
@@ -397,11 +347,11 @@
    public void testSearchRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final SearchRequest searchRequest =
                Requests.newSearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
                newSearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        List<SearchResultEntry> entries = new LinkedList<SearchResultEntry>();
        try {
            mockConnection.search(searchRequest, entries);
            TestCaseUtils.failWasExpected(ErrorResultException.class);
            failWasExpected(ErrorResultException.class);
        } catch (ErrorResultException e) {
            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.UNWILLING_TO_PERFORM);
            assertThat(entries.isEmpty());
@@ -410,36 +360,37 @@
    @Test()
    public void testSingleEntrySearchRequestSuccess() throws Exception {
        final SearchResultEntry entry = Responses.newSearchResultEntry("cn=test");
        final SearchResultEntry entry = newSearchResultEntry("cn=test");
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS, entry);
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
                newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        assertThat(mockConnection.searchSingleEntry(request)).isEqualTo(entry);
    }
    @SuppressWarnings("unchecked")
    @Test()
    public void testSingleEntrySearchAsyncRequestSuccess() throws Exception {
        final SearchResultEntry entry = Responses.newSearchResultEntry("cn=test");
        final SearchResultEntry entry = newSearchResultEntry("cn=test");
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS, entry);
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
                newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        SuccessHandler<SearchResultEntry> successHandler = mock(SuccessHandler.class);
        FutureResult<SearchResultEntry> futureResult = mockConnection.searchSingleEntryAsync(request, handler);
        FutureResult<SearchResultEntry> futureResult = (FutureResult<SearchResultEntry>) mockConnection
                .searchSingleEntryAsync(request).onSuccess(successHandler);
        assertThat(futureResult.get()).isEqualTo(entry);
        verify(handler).handleResult(any(SearchResultEntry.class));
        verify(successHandler).handleResult(any(SearchResultEntry.class));
    }
    @Test()
    public void testSingleEntrySearchRequestNoEntryReturned() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
                newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        try {
            mockConnection.searchSingleEntry(request);
            TestCaseUtils.failWasExpected(EntryNotFoundException.class);
            failWasExpected(EntryNotFoundException.class);
        } catch (EntryNotFoundException e) {
            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED);
        }
@@ -448,12 +399,12 @@
    @Test()
    public void testSingleEntrySearchRequestMultipleEntriesToReturn() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SIZE_LIMIT_EXCEEDED,
                Responses.newSearchResultEntry("cn=test"));
                newSearchResultEntry("cn=test"));
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
                newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        try {
            mockConnection.searchSingleEntry(request);
            TestCaseUtils.failWasExpected(MultipleEntriesFoundException.class);
            failWasExpected(MultipleEntriesFoundException.class);
        } catch (MultipleEntriesFoundException e) {
            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED);
        }
@@ -462,14 +413,13 @@
    @Test()
    public void testSingleEntrySearchRequestMultipleEntriesReturnedByServer() throws Exception {
        // could happen if server does not enforce size limit
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS,
                Responses.newSearchResultEntry("cn=test"),
                Responses.newSearchResultEntry("cn=test,ou=org"));
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest("cn=test", SearchScope.WHOLE_SUBTREE, "(objectClass=*)");
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS, newSearchResultEntry("cn=test"),
                newSearchResultEntry("cn=test,ou=org"));
        final SearchRequest request = newSingleEntrySearchRequest("cn=test", SearchScope.WHOLE_SUBTREE,
                "(objectClass=*)");
        try {
            mockConnection.searchSingleEntry(request);
            TestCaseUtils.failWasExpected(MultipleEntriesFoundException.class);
            failWasExpected(MultipleEntriesFoundException.class);
        } catch (MultipleEntriesFoundException e) {
            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED);
        }
@@ -479,36 +429,35 @@
    @Test()
    public void testSingleEntrySearchAsyncRequestMultipleEntriesToReturn() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.SIZE_LIMIT_EXCEEDED,
                Responses.newSearchResultEntry("cn=test"));
                newSearchResultEntry("cn=test"));
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
                newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        FailureHandler<ErrorResultException> failureHandler = mock(FailureHandler.class);
        try {
            mockConnection.searchSingleEntryAsync(request, handler).get();
            TestCaseUtils.failWasExpected(MultipleEntriesFoundException.class);
            mockConnection.searchSingleEntryAsync(request).onFailure(failureHandler).getOrThrow();
            failWasExpected(MultipleEntriesFoundException.class);
        } catch (MultipleEntriesFoundException e) {
            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED);
            verify(handler).handleErrorResult(any(ErrorResultException.class));
            verify(failureHandler).handleError(any(ErrorResultException.class));
        }
    }
    @Test()
    public void testSingleEntrySearchAsyncRequestMultipleEntriesReturnedByServer() throws Exception {
        // could happen if server does not enfore size limit
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS,
                Responses.newSearchResultEntry("cn=test"),
                Responses.newSearchResultEntry("cn=test,ou=org"));
        final SearchRequest request = Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT,
        final Connection mockConnection = new MockConnection(ResultCode.SUCCESS, newSearchResultEntry("cn=test"),
                newSearchResultEntry("cn=test,ou=org"));
        final SearchRequest request = newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT,
                "(objectClass=*)");
        @SuppressWarnings("unchecked")
        ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
        FailureHandler<ErrorResultException> failureHandler = mock(FailureHandler.class);
        try {
            mockConnection.searchSingleEntryAsync(request, handler).get();
            TestCaseUtils.failWasExpected(MultipleEntriesFoundException.class);
            mockConnection.searchSingleEntryAsync(request).onFailure(failureHandler).getOrThrow();
            failWasExpected(MultipleEntriesFoundException.class);
        } catch (MultipleEntriesFoundException e) {
            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED);
            verify(handler).handleErrorResult(any(ErrorResultException.class));
            verify(failureHandler).handleError(any(ErrorResultException.class));
        }
    }
@@ -516,10 +465,10 @@
    public void testSingleEntrySearchRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
                newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        try {
            mockConnection.searchSingleEntry(request);
            TestCaseUtils.failWasExpected(ErrorResultException.class);
            failWasExpected(ErrorResultException.class);
        } catch (ErrorResultException e) {
            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.UNWILLING_TO_PERFORM);
        }
@@ -529,15 +478,15 @@
    public void testSingleEntrySearchAsyncRequestFail() throws Exception {
        final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
        final SearchRequest request =
                Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
                newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
        @SuppressWarnings("unchecked")
        ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
        FailureHandler<ErrorResultException> failureHandler = mock(FailureHandler.class);
        try {
            mockConnection.searchSingleEntryAsync(request, handler).get();
            TestCaseUtils.failWasExpected(ErrorResultException.class);
            mockConnection.searchSingleEntryAsync(request).onFailure(failureHandler).getOrThrow();
            failWasExpected(ErrorResultException.class);
        } catch (ErrorResultException e) {
            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.UNWILLING_TO_PERFORM);
            verify(handler).handleErrorResult(any(ErrorResultException.class));
            verify(failureHandler).handleError(any(ErrorResultException.class));
        }
    }
opendj-core/src/test/java/org/forgerock/opendj/ldap/AbstractLoadBalancingAlgorithmTestCase.java
@@ -21,28 +21,26 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2013 ForgeRock AS
 *      Copyright 2014 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
import static java.util.Arrays.asList;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.forgerock.opendj.ldap.Connections.newLoadBalancer;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.forgerock.util.promise.Promise;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.forgerock.opendj.util.CompletedFutureResult;
import static java.util.Arrays.*;
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.util.promise.Promises.*;
import static org.mockito.Mockito.*;
@SuppressWarnings("javadoc")
public class AbstractLoadBalancingAlgorithmTestCase extends SdkTestCase {
@@ -59,19 +57,11 @@
            }
            @Override
            public FutureResult<Connection> getConnectionAsync(
                    final ResultHandler<? super Connection> handler) {
            public Promise<Connection, ErrorResultException> getConnectionAsync() {
                try {
                    final Connection connection = mock.getConnection();
                    if (handler != null) {
                        handler.handleResult(connection);
                    }
                    return new CompletedFutureResult<Connection>(connection);
                    return newSuccessfulPromise(mock.getConnection());
                } catch (final ErrorResultException e) {
                    if (handler != null) {
                        handler.handleErrorResult(e);
                    }
                    return new CompletedFutureResult<Connection>(e);
                    return newFailedPromise(e);
                }
            }
opendj-core/src/test/java/org/forgerock/opendj/ldap/ConnectionPoolTestCase.java
@@ -27,18 +27,6 @@
package org.forgerock.opendj.ldap;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldap.Connections.newFixedConnectionPool;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.TestCaseUtils.mockConnection;
import static org.forgerock.opendj.ldap.TestCaseUtils.mockConnectionFactory;
import static org.forgerock.opendj.ldap.TestCaseUtils.mockTimeSource;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -48,9 +36,20 @@
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Responses;
import org.mockito.ArgumentCaptor;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.Assert;
import org.testng.annotations.Test;
import static org.fest.assertions.Assertions.*;
import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
 * Tests the connection pool implementation..
 */
@@ -248,7 +247,7 @@
         * is a connection available immediately then the future will be
         * completed immediately).
         */
        final FutureResult<Connection> future = pool.getConnectionAsync(null);
        final Promise<? extends Connection, ErrorResultException> future = pool.getConnectionAsync();
        assertThat(future.isDone()).isFalse();
        // Release a connection and verify that it is immediately redeemed by
@@ -521,24 +520,31 @@
        final ConnectionFactory factory = mock(ConnectionFactory.class);
        final int poolSize = 2;
        final ConnectionPool pool = Connections.newFixedConnectionPool(factory, poolSize);
        doAnswer(new Answer<Promise<Connection, ErrorResultException>>() {
            @Override
            public Promise<Connection, ErrorResultException> answer(final InvocationOnMock invocation)
                    throws Throwable {
                return PromiseImpl.create();
            }
        }).when(factory).getConnectionAsync();
        List<FutureResult<Connection>> futures = new ArrayList<FutureResult<Connection>>();
        List<Promise<? extends Connection, ErrorResultException>> futures =
                new ArrayList<Promise<? extends Connection, ErrorResultException>>();
        for (int i = 0; i < poolSize + 1; i++) {
            futures.add(pool.getConnectionAsync(null));
            futures.add(pool.getConnectionAsync());
        }
        // factory.getConnectionAsync() has been called by the pool poolSize times
        verify(factory, times(poolSize)).getConnectionAsync();
        final ErrorResultException connectError = ErrorResultException
                .newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR);
        for (Promise<? extends Connection, ErrorResultException> future : futures) {
            // Simulate that an error happened with the created connections
            ((FutureResultImpl) future).handleError(connectError);
        final ArgumentCaptor<ResultHandler> arg = ArgumentCaptor.forClass(ResultHandler.class);
        verify(factory, times(poolSize)).getConnectionAsync(arg.capture());
        final ErrorResultException connectError =
                ErrorResultException.newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR);
        for (ResultHandler<Connection> handler : arg.getAllValues()) {
            handler.handleErrorResult(connectError);
        }
        for (FutureResult<Connection> future : futures) {
            try {
                // Before the fix for OPENDJ-1348 the third future.get() would hang.
                future.get();
                future.getOrThrow();
                Assert.fail("ErrorResultException should have been called");
            } catch (ErrorResultException e) {
                assertThat(e).isSameAs(connectError);
            }
opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2013 ForgeRock AS.
 *      Copyright 2013-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
@@ -33,7 +33,14 @@
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.NeverThrowsException;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.forgerock.util.promise.SuccessHandler;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -42,11 +49,10 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.forgerock.opendj.util.CompletedFutureResult;
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
import static org.forgerock.opendj.ldap.SearchScope.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
@@ -125,43 +131,34 @@
        }
    }
    @SuppressWarnings("unchecked")
    @Test
    public void testBindWhileHeartBeatInProgress() throws Exception {
        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
        mockBindAsyncResponse();
        hbc = hbcf.getConnection();
        /*
         * Send a heartbeat, trapping the search call-back so that we can send
         * the response once we have attempted a bind.
         */
        when(
                connection.searchAsync(any(SearchRequest.class),
                        any(IntermediateResponseHandler.class), any(SearchResultHandler.class)))
                .thenReturn(null);
        when(connection.searchAsync(any(SearchRequest.class), any(SearchResultHandler.class))).thenReturn(
            FutureResultWrapper.newSuccessfulFutureResult(Responses.newResult(ResultCode.SUCCESS)));
        when(hbcf.timeSource.currentTimeMillis()).thenReturn(11000L);
        scheduler.runAllTasks(); // Send the heartbeat.
        // Capture the heartbeat search result handler.
        final ArgumentCaptor<SearchResultHandler> arg =
                ArgumentCaptor.forClass(SearchResultHandler.class);
        verify(connection, times(2)).searchAsync(same(HEARTBEAT),
                any(IntermediateResponseHandler.class), arg.capture());
        verify(connection, times(2)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
        assertThat(hbc.isValid()).isTrue(); // Not checked yet.
        /*
         * Now attempt a bind request, which should be held in a queue until the
         * heart beat completes.
         */
        hbc.bindAsync(newSimpleBindRequest(), null, null);
        verify(connection, times(0)).bindAsync(any(BindRequest.class),
                any(IntermediateResponseHandler.class), any(ResultHandler.class));
        hbc.bindAsync(newSimpleBindRequest());
        verify(connection, times(0)).bindAsync(any(BindRequest.class));
        // Send fake heartbeat response, releasing the bind request.
        arg.getValue().handleResult(newResult(ResultCode.SUCCESS));
        verify(connection, times(1)).bindAsync(any(BindRequest.class),
                any(IntermediateResponseHandler.class), any(ResultHandler.class));
        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
    }
    @Test
@@ -175,34 +172,42 @@
    @Test
    public void testGetConnectionAsync() throws Exception {
        @SuppressWarnings("unchecked")
        final ResultHandler<Connection> mockResultHandler = mock(ResultHandler.class);
        final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
        hbc = hbcf.getConnectionAsync(mockResultHandler).get();
        hbc = hbcf.getConnectionAsync().onSuccess(mockSuccessHandler).getOrThrow();
        assertThat(hbc).isNotNull();
        assertThat(hbc.isValid()).isTrue();
        verify(mockResultHandler).handleResult(any(Connection.class));
        verifyNoMoreInteractions(mockResultHandler);
        verify(mockSuccessHandler).handleResult(any(Connection.class));
        verifyNoMoreInteractions(mockSuccessHandler);
    }
    @Test
    public void testGetConnectionAsyncWithInitialHeartBeatError() throws Exception {
        @SuppressWarnings("unchecked")
        final ResultHandler<Connection> mockResultHandler = mock(ResultHandler.class);
        ErrorResultException expectedException = null;
        final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
        final PromiseImpl<ErrorResultException, NeverThrowsException> promisedError = PromiseImpl.create();
        mockConnectionWithInitialHeartbeatResult(ResultCode.BUSY);
        Promise<? extends Connection, ErrorResultException> promise = hbcf.getConnectionAsync();
        promise.onSuccess(mockSuccessHandler).onFailure(new FailureHandler<ErrorResultException>() {
            @Override
            public void handleError(ErrorResultException error) {
                promisedError.handleResult(error);
            }
        });
        checkInitialHeartBeatFailure(promisedError.getOrThrow());
        try {
            hbcf.getConnectionAsync(mockResultHandler).get();
            promise.getOrThrow();
            fail("Unexpectedly obtained a connection");
        } catch (final ErrorResultException e) {
            checkInitialHeartBeatFailure(e);
            expectedException = e;
        }
        verify(mockResultHandler).handleErrorResult(expectedException);
        verifyNoMoreInteractions(mockResultHandler);
        verifyNoMoreInteractions(mockSuccessHandler);
    }
    @Test
@@ -251,11 +256,11 @@
        // Attempt to send a new request: it should fail immediately.
        @SuppressWarnings("unchecked")
        final ResultHandler<Result> mockHandler = mock(ResultHandler.class);
        hbc.modifyAsync(newModifyRequest(DN.rootDN()), null, mockHandler);
        final FailureHandler<ErrorResultException> mockHandler = mock(FailureHandler.class);
        hbc.modifyAsync(newModifyRequest(DN.rootDN())).onFailure(mockHandler);
        final ArgumentCaptor<ErrorResultException> arg =
                ArgumentCaptor.forClass(ErrorResultException.class);
        verify(mockHandler).handleErrorResult(arg.capture());
        verify(mockHandler).handleError(arg.capture());
        assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(
                ResultCode.CLIENT_SIDE_SERVER_DOWN);
@@ -282,10 +287,11 @@
        assertThat(hbc.isClosed()).isFalse();
    }
    @SuppressWarnings("unchecked")
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Test(description = "OPENDJ-1348")
    public void testBindPreventsHeartBeatTimeout() throws Exception {
        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
        mockBindAsyncResponse();
        hbc = hbcf.getConnection();
        /*
@@ -293,21 +299,18 @@
         * the response once we have attempted a heartbeat.
         */
        when(hbcf.timeSource.currentTimeMillis()).thenReturn(11000L);
        hbc.bindAsync(newSimpleBindRequest(), null, null);
        @SuppressWarnings("rawtypes")
        final ArgumentCaptor<ResultHandler> arg = ArgumentCaptor.forClass(ResultHandler.class);
        verify(connection, times(1)).bindAsync(any(BindRequest.class),
                any(IntermediateResponseHandler.class), arg.capture());
        FutureResult<BindResult> future = hbc.bindAsync(newSimpleBindRequest());
        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
        // Verify no heartbeat is sent because there is a bind in progress.
        when(hbcf.timeSource.currentTimeMillis()).thenReturn(11001L);
        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
        verify(connection, times(1)).searchAsync(same(HEARTBEAT),
                any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
        verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
        // Send fake bind response, releasing the heartbeat.
        when(hbcf.timeSource.currentTimeMillis()).thenReturn(11099L);
        arg.getValue().handleResult(newResult(ResultCode.SUCCESS));
        ((ResultHandler) future).handleResult(newResult(ResultCode.SUCCESS));
        // Check that bind response acts as heartbeat.
        assertThat(hbc.isValid()).isTrue();
@@ -316,25 +319,21 @@
        assertThat(hbc.isValid()).isTrue();
    }
    @SuppressWarnings("unchecked")
    @Test(description = "OPENDJ-1348")
    public void testBindTriggersHeartBeatTimeoutWhenTooSlow() throws Exception {
        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
        mockBindAsyncResponse();
        hbc = hbcf.getConnection();
        // Send another bind request which will timeout.
        when(hbcf.timeSource.currentTimeMillis()).thenReturn(20000L);
        hbc.bindAsync(newSimpleBindRequest(), null, null);
        @SuppressWarnings("rawtypes")
        final ArgumentCaptor<ResultHandler> arg = ArgumentCaptor.forClass(ResultHandler.class);
        verify(connection, times(1)).bindAsync(any(BindRequest.class),
                any(IntermediateResponseHandler.class), arg.capture());
        hbc.bindAsync(newSimpleBindRequest());
        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
        // Verify no heartbeat is sent because there is a bind in progress.
        when(hbcf.timeSource.currentTimeMillis()).thenReturn(20001L);
        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
        verify(connection, times(1)).searchAsync(same(HEARTBEAT),
                any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
        verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
        // Check that lack of bind response acts as heartbeat timeout.
        assertThat(hbc.isValid()).isTrue();
@@ -343,41 +342,39 @@
        assertThat(hbc.isValid()).isFalse();
    }
    @SuppressWarnings("unchecked")
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Test
    public void testHeartBeatWhileBindInProgress() throws Exception {
        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
        mockBindAsyncResponse();
        hbc = hbcf.getConnection();
        /*
         * Send a bind request, trapping the bind call-back so that we can send
         * the response once we have attempted a heartbeat.
         */
        hbc.bindAsync(newSimpleBindRequest(), null, null);
        FutureResult result = hbc.bindAsync(newSimpleBindRequest());
        // Capture the bind result handler.
        @SuppressWarnings("rawtypes")
        final ArgumentCaptor<ResultHandler> arg = ArgumentCaptor.forClass(ResultHandler.class);
        verify(connection, times(1)).bindAsync(any(BindRequest.class),
                any(IntermediateResponseHandler.class), arg.capture());
        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
        /*
         * Now attempt the heartbeat which should not happen because there is a
         * bind in progress.
         */
        when(hbcf.timeSource.currentTimeMillis()).thenReturn(11000L);
        scheduler.runAllTasks(); // Attempt to send the heartbeat.
        verify(connection, times(1)).searchAsync(same(HEARTBEAT),
                any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
        // Attempt to send the heartbeat.
        scheduler.runAllTasks();
        verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
        // Send fake bind response, releasing the heartbeat.
        arg.getValue().handleResult(newResult(ResultCode.SUCCESS));
        ((ResultHandler) result).handleResult(newResult(ResultCode.SUCCESS));
        // Attempt to send a heartbeat again.
        when(hbcf.timeSource.currentTimeMillis()).thenReturn(16000L);
        scheduler.runAllTasks(); // Attempt to send the heartbeat.
        verify(connection, times(2)).searchAsync(same(HEARTBEAT),
                any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
        // Attempt to send the heartbeat.
        scheduler.runAllTasks();
        verify(connection, times(2)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
    }
    @Test
@@ -417,43 +414,46 @@
        hbcf.timeSource = mockTimeSource(0);
    }
    private Connection mockHeartBeatResponse(final Connection mockConnection,
            final List<ConnectionEventListener> listeners, final ResultCode resultCode) {
    private void mockBindAsyncResponse() {
        doAnswer(new Answer<FutureResult<Result>>() {
            @Override
            public FutureResult<Result> answer(final InvocationOnMock invocation) throws Throwable {
                return new FutureResultImpl<Result>();
            }
        }).when(connection).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
    }
    private Connection mockHeartBeatResponse(final Connection mockConnection,
        final List<ConnectionEventListener> listeners, final ResultCode resultCode) {
        Answer<FutureResult<Result>> answer = new Answer<FutureResult<Result>>() {
            @Override
            public FutureResult<Result> answer(final InvocationOnMock invocation) throws Throwable {
                if (resultCode == null) {
                    return null;
                }
                final SearchResultHandler handler =
                        (SearchResultHandler) invocation.getArguments()[2];
                if (resultCode.isExceptional()) {
                    final ErrorResultException error = newErrorResult(resultCode);
                    if (handler != null) {
                        handler.handleErrorResult(error);
                    }
                    if (error instanceof ConnectionException) {
                        for (final ConnectionEventListener listener : listeners) {
                            listener.handleConnectionError(false, error);
                        }
                    }
                    return new CompletedFutureResult<Result>(error);
                    return newFailedFutureResult(error);
                } else {
                    final Result result = newResult(resultCode);
                    if (handler != null) {
                        handler.handleResult(result);
                    }
                    return new CompletedFutureResult<Result>(result);
                    return newSuccessfulFutureResult(newResult(resultCode));
                }
            }
        }).when(mockConnection).searchAsync(any(SearchRequest.class),
                any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
        };
        doAnswer(answer).when(mockConnection).searchAsync(any(SearchRequest.class), any(SearchResultHandler.class));
        doAnswer(answer).when(mockConnection).searchAsync(any(SearchRequest.class),
            any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
        return mockConnection;
    }
    private void verifyHeartBeatSent(final Connection connection, final int times) {
        verify(connection, times(times)).searchAsync(same(HEARTBEAT),
                any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
        verify(connection, times(times)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
    }
}
opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
@@ -27,8 +27,6 @@
package org.forgerock.opendj.ldap;
import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
@@ -78,6 +76,8 @@
import com.forgerock.opendj.ldap.controls.AccountUsabilityRequestControl;
import com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
/**
 * A simple ldap server that manages 1000 entries and used for running
 * testcases.
@@ -100,19 +100,23 @@
            this.isCanceled = new AtomicBoolean(false);
        }
        @Override
        public Request addControl(final Control cntrl) {
            return request.addControl(cntrl);
        }
        @Override
        public boolean containsControl(final String oid) {
            return request.containsControl(oid);
        }
        @Override
        public <C extends Control> C getControl(final ControlDecoder<C> decoder,
                final DecodeOptions options) throws DecodeException {
            return request.getControl(decoder, options);
        }
        @Override
        public List<Control> getControls() {
            return request.getControls();
        }
@@ -147,6 +151,7 @@
            this.clientContext = clientContext;
        }
        @Override
        public void handleAbandon(final Integer context, final AbandonRequest request)
                throws UnsupportedOperationException {
            // Check if we have any concurrent operation with this message id.
@@ -160,6 +165,7 @@
            // No response is needed.
        }
        @Override
        public void handleAdd(final Integer context, final AddRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<Result> handler) throws UnsupportedOperationException {
@@ -172,7 +178,7 @@
                // duplicate entry.
                result = Responses.newResult(ResultCode.ENTRY_ALREADY_EXISTS);
                final ErrorResultException ere = ErrorResultException.newErrorResult(result);
                handler.handleErrorResult(ere);
                handler.handleError(ere);
                // doesn't matter if it was canceled.
                requestsInProgress.remove(context);
                return;
@@ -191,7 +197,7 @@
            if (abReq.isCanceled()) {
                result = Responses.newResult(ResultCode.CANCELLED);
                final ErrorResultException ere = ErrorResultException.newErrorResult(result);
                handler.handleErrorResult(ere);
                handler.handleError(ere);
                requestsInProgress.remove(context);
                return;
            }
@@ -202,6 +208,7 @@
            handler.handleResult(result);
        }
        @Override
        public void handleBind(final Integer context, final int version, final BindRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<BindResult> resultHandler) throws UnsupportedOperationException {
@@ -229,6 +236,7 @@
                                Sasl.createSaslServer(saslMech, "ldap",
                                        listener.getHostName(), props,
                                        new CallbackHandler() {
                                            @Override
                                            public void handle(Callback[] callbacks)
                                                    throws IOException,
                                                    UnsupportedCallbackException {
@@ -264,6 +272,7 @@
                                && (qop.equalsIgnoreCase("auth-int") || qop
                                        .equalsIgnoreCase("auth-conf"))) {
                            ConnectionSecurityLayer csl = new ConnectionSecurityLayer() {
                                @Override
                                public void dispose() {
                                    try {
                                        saslServer.dispose();
@@ -272,6 +281,7 @@
                                    }
                                }
                                @Override
                                public byte[] unwrap(byte[] incoming, int offset, int len)
                                        throws ErrorResultException {
                                    try {
@@ -283,6 +293,7 @@
                                    }
                                }
                                @Override
                                public byte[] wrap(byte[] outgoing, int offset, int len)
                                        throws ErrorResultException {
                                    try {
@@ -304,7 +315,7 @@
                                ByteString.wrap(challenge)));
                    }
                } catch (Exception e) {
                    resultHandler.handleErrorResult(ErrorResultException.newErrorResult(Responses
                    resultHandler.handleError(ErrorResultException.newErrorResult(Responses
                            .newBindResult(ResultCode.OPERATIONS_ERROR).setCause(e)
                            .setDiagnosticMessage(e.toString())));
                }
@@ -314,6 +325,7 @@
            requestsInProgress.remove(context);
        }
        @Override
        public void handleConnectionClosed(final Integer context, final UnbindRequest request) {
            close();
        }
@@ -328,14 +340,17 @@
            }
        }
        @Override
        public void handleConnectionDisconnected(ResultCode resultCode, String message) {
            close();
        }
        @Override
        public void handleConnectionError(final Throwable error) {
            close();
        }
        @Override
        public void handleCompare(final Integer context, final CompareRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<CompareResult> resultHandler)
@@ -349,7 +364,7 @@
                // entry not found.
                result = Responses.newCompareResult(ResultCode.NO_SUCH_ATTRIBUTE);
                final ErrorResultException ere = ErrorResultException.newErrorResult(result);
                resultHandler.handleErrorResult(ere);
                resultHandler.handleError(ere);
                // doesn't matter if it was canceled.
                requestsInProgress.remove(context);
                return;
@@ -365,7 +380,7 @@
                    if (abReq.isCanceled()) {
                        final Result r = Responses.newResult(ResultCode.CANCELLED);
                        final ErrorResultException ere = ErrorResultException.newErrorResult(r);
                        resultHandler.handleErrorResult(ere);
                        resultHandler.handleError(ere);
                        requestsInProgress.remove(context);
                        return;
                    }
@@ -382,6 +397,7 @@
            requestsInProgress.remove(context);
        }
        @Override
        public void handleDelete(final Integer context, final DeleteRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<Result> handler) throws UnsupportedOperationException {
@@ -394,7 +410,7 @@
                // entry is not found.
                result = Responses.newResult(ResultCode.NO_SUCH_OBJECT);
                final ErrorResultException ere = ErrorResultException.newErrorResult(result);
                handler.handleErrorResult(ere);
                handler.handleError(ere);
                // doesn't matter if it was canceled.
                requestsInProgress.remove(context);
                return;
@@ -403,7 +419,7 @@
            if (abReq.isCanceled()) {
                result = Responses.newResult(ResultCode.CANCELLED);
                final ErrorResultException ere = ErrorResultException.newErrorResult(result);
                handler.handleErrorResult(ere);
                handler.handleError(ere);
                requestsInProgress.remove(context);
                return;
            }
@@ -414,6 +430,7 @@
            handler.handleResult(result);
        }
        @Override
        public <R extends ExtendedResult> void handleExtendedRequest(final Integer context,
                final ExtendedRequest<R> request,
                final IntermediateResponseHandler intermediateResponseHandler,
@@ -428,21 +445,24 @@
            }
        }
        @Override
        public void handleModify(final Integer context, final ModifyRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<Result> resultHandler) throws UnsupportedOperationException {
            // TODO:
        }
        @Override
        public void handleModifyDN(final Integer context, final ModifyDNRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<Result> resultHandler) throws UnsupportedOperationException {
            // TODO
        }
        @Override
        public void handleSearch(final Integer context, final SearchRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final SearchResultHandler resultHandler) throws UnsupportedOperationException {
            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler,
            final ResultHandler<Result> resultHandler) throws UnsupportedOperationException {
            Result result = null;
            final AbandonableRequest abReq = new AbandonableRequest(request);
            requestsInProgress.put(context, abReq);
@@ -452,7 +472,7 @@
                // Entry not found.
                result = Responses.newResult(ResultCode.NO_SUCH_OBJECT);
                final ErrorResultException ere = ErrorResultException.newErrorResult(result);
                resultHandler.handleErrorResult(ere);
                resultHandler.handleError(ere);
                // Should searchResultHandler handle anything?
                // doesn't matter if it was canceled.
@@ -463,21 +483,19 @@
            if (abReq.isCanceled()) {
                result = Responses.newResult(ResultCode.CANCELLED);
                final ErrorResultException ere = ErrorResultException.newErrorResult(result);
                resultHandler.handleErrorResult(ere);
                resultHandler.handleError(ere);
                requestsInProgress.remove(context);
                return;
            }
            final SearchResultEntry e =
                    Responses.newSearchResultEntry(new LinkedHashMapEntry(entryMap.get(dn)));
            final SearchResultEntry e = Responses.newSearchResultEntry(new LinkedHashMapEntry(entryMap.get(dn)));
            // Check we have had any controls in the request.
            for (final Control control : request.getControls()) {
                if (control.getOID().equals(AccountUsabilityRequestControl.OID)) {
                    e.addControl(AccountUsabilityResponseControl.newControl(false, false, false,
                            10, false, 0));
                    e.addControl(AccountUsabilityResponseControl.newControl(false, false, false, 10, false, 0));
                }
            }
            resultHandler.handleEntry(e);
            entryHandler.handleEntry(e);
            result = Responses.newResult(ResultCode.SUCCESS);
            resultHandler.handleResult(result);
            requestsInProgress.remove(context);
@@ -521,6 +539,7 @@
        }
    }
    @Override
    public ServerConnection<Integer> handleAccept(final LDAPClientContext context) {
        return new LDAPServerConnection(context);
    }
opendj-core/src/test/java/org/forgerock/opendj/ldap/TestCaseUtils.java
@@ -26,12 +26,6 @@
 */
package org.forgerock.opendj.ldap;
import static org.fest.assertions.Fail.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
@@ -42,13 +36,18 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import org.forgerock.util.promise.Promise;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.OngoingStubbing;
import com.forgerock.opendj.util.CompletedFutureResult;
import com.forgerock.opendj.util.TimeSource;
import static org.fest.assertions.Fail.*;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
 * This class defines some utility functions which can be used by test cases.
 */
@@ -164,30 +163,20 @@
     *            The remaining connections to return.
     * @return The connection factory.
     */
    @SuppressWarnings("unchecked")
    public static ConnectionFactory mockConnectionFactory(final Connection first,
            final Connection... remaining) {
    public static ConnectionFactory mockConnectionFactory(final Connection first, final Connection... remaining) {
        final ConnectionFactory factory = mock(ConnectionFactory.class);
        try {
            when(factory.getConnection()).thenReturn(first, remaining);
        } catch (ErrorResultException ignored) {
            // Cannot happen.
        }
        when(factory.getConnectionAsync(any(ResultHandler.class))).thenAnswer(
                new Answer<FutureResult<Connection>>() {
                    @Override
                    public FutureResult<Connection> answer(final InvocationOnMock invocation)
                            throws Throwable {
                        final Connection connection = factory.getConnection();
                        // Execute handler and return future.
                        final ResultHandler<? super Connection> handler =
                                (ResultHandler<? super Connection>) invocation.getArguments()[0];
                        if (handler != null) {
                            handler.handleResult(connection);
                        }
                        return new CompletedFutureResult<Connection>(connection);
                    }
                });
        when(factory.getConnectionAsync()).thenAnswer(new Answer<Promise<Connection, ErrorResultException>>() {
            @Override
            public Promise<Connection, ErrorResultException> answer(final InvocationOnMock invocation)
                    throws Throwable {
                return newSuccessfulFutureResult(factory.getConnection());
            }
        });
        return factory;
    }
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SchemaBuilderTestCase.java
@@ -25,31 +25,33 @@
 */
package org.forgerock.opendj.ldap.schema;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.SCHEMA_PROPERTY_ORIGIN;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.EntryNotFoundException;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.FutureResultWrapper;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.testng.annotations.Test;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.util.promise.Promise;
import org.testng.annotations.Test;
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.*;
import static org.forgerock.util.promise.Promises.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
 * Test SchemaBuilder.
@@ -1969,6 +1971,55 @@
        connection.close();
    }
    /**
     * Asynchronously retrieving an LDAP Server's schema.
     *
     * @throws Exception
     */
    @Test()
    public final void testSchemaBuilderAddSchemaForEntryAsyncMockConnection() throws Exception {
        Connection connection = mock(Connection.class);
        final SchemaBuilder scBuild = new SchemaBuilder(Schema.getCoreSchema());
        // @formatter:off
        final String[] entry = {
            "# Search result entry: uid=bjensen,ou=People,dc=example,dc=com",
            "dn: uid=bjensen,ou=People,dc=example,dc=com",
            "subschemaSubentry: cn=schema",
            "entryDN: uid=bjensen,ou=people,dc=example,dc=com",
            "entryUUID: fc252fd9-b982-3ed6-b42a-c76d2546312c"
            // N.B : also works with previous example but needs the subschemaSubentry line.
        };
        // Send a search entry result promise :
        Promise<SearchResultEntry, ErrorResultException> promise =
                newSuccessfulPromise(Responses.newSearchResultEntry(entry));
        FutureResult<SearchResultEntry> result = FutureResultWrapper.asFutureResult(promise);
        when(connection.searchSingleEntryAsync((SearchRequest) any())).thenReturn(result);
        DN testDN = DN.valueOf("uid=bjensen,ou=People,dc=example,dc=com");
        // @formatter:on
        Schema sc = scBuild.addSchemaForEntryAsync(connection, testDN, false).getOrThrow().toSchema();
        // We retrieve the schema
        assertThat(sc.getSyntaxes()).isNotNull();
        assertThat(sc.getAttributeTypes()).isNotNull();
        assertThat(sc.getAttributeTypes()).isNotEmpty();
        assertThat(sc.getObjectClasses()).isNotNull();
        assertThat(sc.getObjectClasses()).isNotEmpty();
        assertThat(sc.getMatchingRuleUses()).isNotNull();
        assertThat(sc.getMatchingRuleUses()).isEmpty();
        assertThat(sc.getMatchingRules()).isNotNull();
        assertThat(sc.getMatchingRules()).isNotEmpty();
        assertThat(sc.getDITContentRules()).isNotNull();
        assertThat(sc.getDITContentRules()).isEmpty();
        assertThat(sc.getDITStuctureRules()).isNotNull();
        assertThat(sc.getDITStuctureRules()).isEmpty();
        assertThat(sc.getNameForms()).isNotNull();
        assertThat(sc.getNameForms()).isEmpty();
        connection.close();
    }
    @Test
    public void testDefaultSyntax() {
        final Schema schema =
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/SchemaTestCase.java
@@ -26,7 +26,19 @@
package org.forgerock.opendj.ldap.schema;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.util.promise.Promises.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.FutureResultWrapper;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.util.promise.Promise;
import org.testng.annotations.Test;
/**
@@ -50,4 +62,54 @@
        final Schema strictSchema2 = schema.asStrictSchema().asNonStrictSchema().asStrictSchema();
        assertThat(strictSchema1).isSameAs(strictSchema2);
    }
    /**
     * Asynchronously retrieving a simple schema.
     *
     * @throws Exception
     */
    @Test()
    public final void testReadSchemaAsyncMethodsMockConnection() throws Exception {
        Connection connection = mock(Connection.class);
        // @formatter:off
        final String[] entry = {
            "# Search result entry: uid=bjensen,ou=People,dc=example,dc=com",
            "dn: uid=bjensen,ou=People,dc=example,dc=com",
            "subschemaSubentry: cn=schema",
            "entryDN: uid=bjensen,ou=people,dc=example,dc=com",
            "entryUUID: fc252fd9-b982-3ed6-b42a-c76d2546312c"
            // N.B : also works with previous example but needs the subschemaSubentry line.
        };
        // Send a search entry result promise :
        Promise<SearchResultEntry, ErrorResultException> promise =
                newSuccessfulPromise(Responses.newSearchResultEntry(entry));
        FutureResult<SearchResultEntry> result = FutureResultWrapper.asFutureResult(promise);
        when(connection.searchSingleEntryAsync((SearchRequest) any())).thenReturn(result);
        DN testDN = DN.valueOf("uid=bjensen,ou=People,dc=example,dc=com");
        // @formatter:on
        Schema[] schemas = new Schema[] {
                Schema.readSchemaAsync(connection, testDN).getOrThrow(),
                Schema.readSchemaForEntryAsync(connection, testDN).getOrThrow()
        };
        // We retrieve the schemas :
        for (Schema sc : schemas) {
            assertThat(sc.getSyntaxes()).isNotNull();
            assertThat(sc.getAttributeTypes()).isNotNull();
            assertThat(sc.getObjectClasses()).isNotNull();
            assertThat(sc.getMatchingRuleUses()).isNotNull();
            assertThat(sc.getMatchingRuleUses()).isEmpty();
            assertThat(sc.getMatchingRules()).isNotNull();
            assertThat(sc.getDITContentRules()).isNotNull();
            assertThat(sc.getDITContentRules()).isEmpty();
            assertThat(sc.getDITStuctureRules()).isNotNull();
            assertThat(sc.getDITStuctureRules()).isEmpty();
            assertThat(sc.getNameForms()).isNotNull();
            assertThat(sc.getNameForms()).isEmpty();
        }
        connection.close();
    }
}
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPConnectionFactory.java
@@ -26,19 +26,17 @@
package org.forgerock.opendj.ldap.spi;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.mockito.Mockito.mock;
import java.net.InetSocketAddress;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.util.promise.Promise;
import com.forgerock.opendj.util.AsynchronousFutureResult;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.util.promise.Promises.*;
import static org.mockito.Mockito.*;
/**
 * Basic LDAP connection factory implementation to use for tests only.
@@ -73,19 +71,15 @@
    @Override
    public Connection getConnection() throws ErrorResultException {
        try {
            return getConnectionAsync(null).get();
            return getConnectionAsync().getOrThrow();
        } catch (final InterruptedException e) {
            throw newErrorResult(ResultCode.CLIENT_SIDE_USER_CANCELLED, e);
        }
    }
    @Override
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> handler) {
        final AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> future =
                new AsynchronousFutureResult<Connection, ResultHandler<? super Connection>>(handler);
        future.handleResult(mock(Connection.class));
        return future;
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        return newSuccessfulPromise(mock(Connection.class));
    }
    /**
@@ -93,6 +87,7 @@
     *
     * @return The address of the Directory Server.
     */
    @Override
    public InetSocketAddress getSocketAddress() {
        return new InetSocketAddress(host, port);
    }
opendj-core/src/test/java/org/forgerock/opendj/ldif/ConnectionEntryReaderTestCase.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2011 ForgeRock AS
 *      Portions copyright 2012 ForgeRock AS.
 *      Portions copyright 2012-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldif;
@@ -34,7 +34,6 @@
import static org.forgerock.opendj.ldap.responses.Responses.newSearchResultEntry;
import static org.forgerock.opendj.ldap.responses.Responses.newSearchResultReference;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.isNull;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -44,7 +43,7 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ErrorResultIOException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.FutureResultWrapper;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchResultReferenceIOException;
@@ -58,8 +57,6 @@
import org.mockito.stubbing.Answer;
import org.testng.annotations.Test;
import com.forgerock.opendj.util.CompletedFutureResult;
/**
 * This class tests the ConnectionEntryReader functionality.
 */
@@ -254,37 +251,30 @@
    private ConnectionEntryReader newReader(final Object... responses) {
        final Connection connection = mock(Connection.class);
        // @formatter:off
        when(connection.searchAsync(same(SEARCH), (IntermediateResponseHandler) isNull(),
                any(SearchResultHandler.class))).thenAnswer(
                        new Answer<FutureResult<Result>>() {
                            @Override
                            public FutureResult<Result> answer(final InvocationOnMock invocation)
                                    throws Throwable {
                                // Execute handler and return future.
                                final SearchResultHandler handler =
                                        (SearchResultHandler) invocation.getArguments()[2];
                                if (handler != null) {
                                    for (int i = 0; i < responses.length; i++) {
                                        final Object response = responses[i];
                                        if (response instanceof SearchResultEntry) {
                                            handler.handleEntry((SearchResultEntry) response);
                                        } else if (response instanceof SearchResultReference) {
                                            handler.handleReference((SearchResultReference) response);
                                        } else if (((Result) response).isSuccess()) {
                                            handler.handleResult((Result) response);
                                        } else {
                                            handler.handleErrorResult(newErrorResult((Result) response));
                                        }
                                    }
                                }
                                final Result result = (Result) responses[responses.length - 1];
                                if (result.isSuccess()) {
                                    return new CompletedFutureResult<Result>(result);
                                } else {
                                    return new CompletedFutureResult<Result>(newErrorResult(result));
                                }
        when(connection.searchAsync(same(SEARCH), any(SearchResultHandler.class))).thenAnswer(
            new Answer<FutureResult<Result>>() {
                @Override
                public FutureResult<Result> answer(final InvocationOnMock invocation) throws Throwable {
                    // Execute handler and return future.
                    final SearchResultHandler handler = (SearchResultHandler) invocation.getArguments()[1];
                    if (handler != null) {
                        for (int i = 0; i < responses.length; i++) {
                            final Object response = responses[i];
                            if (response instanceof SearchResultEntry) {
                                handler.handleEntry((SearchResultEntry) response);
                            } else if (response instanceof SearchResultReference) {
                                handler.handleReference((SearchResultReference) response);
                            }
                        });
                        }
                    }
                    final Result result = (Result) responses[responses.length - 1];
                    if (result.isSuccess()) {
                        return FutureResultWrapper.newSuccessfulFutureResult(result);
                    } else {
                        return FutureResultWrapper.newFailedFutureResult(newErrorResult(result));
                    }
                }
            });
        // @formatter:on
        return new ConnectionEntryReader(connection, SEARCH);
    }
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
@@ -26,9 +26,6 @@
 */
package org.forgerock.opendj.grizzly;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.*;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
@@ -52,7 +49,6 @@
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.TimeoutEventListener;
@@ -81,15 +77,17 @@
import org.forgerock.opendj.ldap.spi.LDAPExtendedFutureResultImpl;
import org.forgerock.opendj.ldap.spi.LDAPFutureResultImpl;
import org.forgerock.opendj.ldap.spi.LDAPSearchFutureResultImpl;
import org.forgerock.util.Reject;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.grizzly.ssl.SSLFilter;
import com.forgerock.opendj.util.CompletedFutureResult;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
import org.forgerock.util.Reject;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.*;
/**
 * LDAP connection implementation.
@@ -120,12 +118,12 @@
    private final ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>> pendingRequests =
            new ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>>();
    private final Object stateLock = new Object();
    // Guarded by stateLock
    /** Guarded by stateLock. */
    private Result connectionInvalidReason;
    private boolean failedDueToDisconnect = false;
    private boolean isClosed = false;
    private boolean isFailed = false;
    private List<ConnectionEventListener> listeners = null;
    private boolean failedDueToDisconnect;
    private boolean isClosed;
    private boolean isFailed;
    private List<ConnectionEventListener> listeners;
    /**
     * Create a LDAP Connection with provided Grizzly connection and LDAP
@@ -162,19 +160,18 @@
                checkBindOrStartTLSInProgress();
            }
        } catch (final ErrorResultException e) {
            return new CompletedFutureResult<Void>(e);
            return newFailedFutureResult(e);
        }
        // Remove the future associated with the request to be abandoned.
        final AbstractLDAPFutureResultImpl<?> pendingRequest =
                pendingRequests.remove(request.getRequestID());
        final AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests.remove(request.getRequestID());
        if (pendingRequest == null) {
            /*
             * There has never been a request with the specified message ID or
             * the response has already been received and handled. We can ignore
             * this abandon request.
             */
            return new CompletedFutureResult<Void>((Void) null);
            return newSuccessfulFutureResult((Void) null);
        }
        /*
@@ -197,9 +194,9 @@
            final int messageID = nextMsgID.getAndIncrement();
            writer.writeAbandonRequest(messageID, request);
            connection.write(writer.getASN1Writer().getBuffer(), null);
            return new CompletedFutureResult<Void>((Void) null, messageID);
            return newSuccessfulFutureResult((Void) null, messageID);
        } catch (final IOException e) {
            return new CompletedFutureResult<Void>(adaptRequestIOException(e));
            return newFailedFutureResult(adaptRequestIOException(e));
        } finally {
            GrizzlyUtils.recycleWriter(writer);
        }
@@ -207,12 +204,10 @@
    @Override
    public FutureResult<Result> addAsync(final AddRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int messageID = nextMsgID.getAndIncrement();
        final LDAPFutureResultImpl future =
                new LDAPFutureResultImpl(messageID, request, resultHandler,
                        intermediateResponseHandler, this);
                new LDAPFutureResultImpl(messageID, request, intermediateResponseHandler, this);
        try {
            synchronized (stateLock) {
                checkConnectionIsValid();
@@ -264,42 +259,35 @@
    @Override
    public FutureResult<BindResult> bindAsync(final BindRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super BindResult> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int messageID = nextMsgID.getAndIncrement();
        final BindClient context;
        try {
            context =
                    request.createBindClient(Connections.getHostString(factory.getSocketAddress()));
            context = request.createBindClient(Connections.getHostString(factory.getSocketAddress()));
        } catch (final Exception e) {
            // FIXME: I18N need to have a better error message.
            // FIXME: Is this the best result code?
            final Result errorResult =
                    Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
                            "An error occurred while creating a bind context").setCause(e);
            final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
                    .setDiagnosticMessage("An error occurred while creating a bind context").setCause(e);
            final ErrorResultException error = ErrorResultException.newErrorResult(errorResult);
            if (resultHandler != null) {
                resultHandler.handleErrorResult(error);
            }
            return new CompletedFutureResult<BindResult>(error, messageID);
            return newFailedFutureResult(error, messageID);
        }
        final LDAPBindFutureResultImpl future =
                new LDAPBindFutureResultImpl(messageID, context, resultHandler,
                        intermediateResponseHandler, this);
                new LDAPBindFutureResultImpl(messageID, context, intermediateResponseHandler, this);
        try {
            synchronized (stateLock) {
                checkConnectionIsValid();
                if (!pendingRequests.isEmpty()) {
                    future.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
                            .setDiagnosticMessage(
                                    "There are other operations pending on this connection"));
                    future.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
                            "There are other operations pending on this connection"));
                    return future;
                }
                if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
                    future.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
                            .setDiagnosticMessage("Bind or Start TLS operation in progress"));
                    future.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
                            "Bind or Start TLS operation in progress"));
                    return future;
                }
                pendingRequests.put(messageID, future);
@@ -338,12 +326,10 @@
    @Override
    public FutureResult<CompareResult> compareAsync(final CompareRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super CompareResult> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int messageID = nextMsgID.getAndIncrement();
        final LDAPCompareFutureResultImpl future =
                new LDAPCompareFutureResultImpl(messageID, request, resultHandler,
                        intermediateResponseHandler, this);
                new LDAPCompareFutureResultImpl(messageID, request, intermediateResponseHandler, this);
        try {
            synchronized (stateLock) {
                checkConnectionIsValid();
@@ -370,12 +356,10 @@
    @Override
    public FutureResult<Result> deleteAsync(final DeleteRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int messageID = nextMsgID.getAndIncrement();
        final LDAPFutureResultImpl future =
                new LDAPFutureResultImpl(messageID, request, resultHandler,
                        intermediateResponseHandler, this);
                new LDAPFutureResultImpl(messageID, request, intermediateResponseHandler, this);
        try {
            synchronized (stateLock) {
                checkConnectionIsValid();
@@ -401,32 +385,26 @@
    }
    @Override
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
            final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super R> resultHandler) {
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int messageID = nextMsgID.getAndIncrement();
        final LDAPExtendedFutureResultImpl<R> future =
                new LDAPExtendedFutureResultImpl<R>(messageID, request, resultHandler,
                        intermediateResponseHandler, this);
                new LDAPExtendedFutureResultImpl<R>(messageID, request, intermediateResponseHandler, this);
        try {
            synchronized (stateLock) {
                checkConnectionIsValid();
                if (request.getOID().equals(StartTLSExtendedRequest.OID)) {
                if (StartTLSExtendedRequest.OID.equals(request.getOID())) {
                    if (!pendingRequests.isEmpty()) {
                        future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
                                ResultCode.OPERATIONS_ERROR, "",
                                "There are pending operations on this connection"));
                                ResultCode.OPERATIONS_ERROR, "", "There are pending operations on this connection"));
                        return future;
                    } else if (isTLSEnabled()) {
                        future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
                                ResultCode.OPERATIONS_ERROR, "",
                                "This connection is already TLS enabled"));
                                ResultCode.OPERATIONS_ERROR, "", "This connection is already TLS enabled"));
                        return future;
                    } else if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
                        future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
                                ResultCode.OPERATIONS_ERROR, "",
                                "Bind or Start TLS operation in progress"));
                                ResultCode.OPERATIONS_ERROR, "", "Bind or Start TLS operation in progress"));
                        return future;
                    }
                } else {
@@ -469,12 +447,10 @@
    @Override
    public FutureResult<Result> modifyAsync(final ModifyRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int messageID = nextMsgID.getAndIncrement();
        final LDAPFutureResultImpl future =
                new LDAPFutureResultImpl(messageID, request, resultHandler,
                        intermediateResponseHandler, this);
                new LDAPFutureResultImpl(messageID, request, intermediateResponseHandler, this);
        try {
            synchronized (stateLock) {
                checkConnectionIsValid();
@@ -501,12 +477,10 @@
    @Override
    public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<? super Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        final int messageID = nextMsgID.getAndIncrement();
        final LDAPFutureResultImpl future =
                new LDAPFutureResultImpl(messageID, request, resultHandler,
                        intermediateResponseHandler, this);
                new LDAPFutureResultImpl(messageID, request, intermediateResponseHandler, this);
        try {
            synchronized (stateLock) {
                checkConnectionIsValid();
@@ -541,14 +515,13 @@
        }
    }
    /** {@inheritDoc} */
    @Override
    public FutureResult<Result> searchAsync(final SearchRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final SearchResultHandler resultHandler) {
        final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
        final int messageID = nextMsgID.getAndIncrement();
        final LDAPSearchFutureResultImpl future =
                new LDAPSearchFutureResultImpl(messageID, request, resultHandler,
                        intermediateResponseHandler, this);
            new LDAPSearchFutureResultImpl(messageID, request, entryHandler, intermediateResponseHandler, this);
        try {
            synchronized (stateLock) {
                checkConnectionIsValid();
@@ -614,23 +587,18 @@
                 */
                logger.debug(LocalizableMessage.raw("Failing bind or StartTLS request due to timeout %s"
                        + "(connection will be invalidated): ", future));
                final Result result =
                        Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
                                LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT.get(timeout)
                                        .toString());
                final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
                        LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT.get(timeout).toString());
                future.adaptErrorResult(result);
                // Fail the connection.
                final Result errorResult =
                        Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
                                LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT.get(timeout)
                                        .toString());
                final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
                        LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT.get(timeout).toString());
                connectionErrorOccurred(errorResult);
            } else {
                logger.debug(LocalizableMessage.raw("Failing request due to timeout: %s", future));
                final Result result =
                        Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
                                LDAP_CONNECTION_REQUEST_TIMEOUT.get(timeout).toString());
                final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
                        LDAP_CONNECTION_REQUEST_TIMEOUT.get(timeout).toString());
                future.adaptErrorResult(result);
                /*
@@ -640,9 +608,9 @@
                 * request while holding the state lock, since a blocking write
                 * could hang the application.
                 */
//              if (!bindOrStartTLSInProgress.get()) {
//                  sendAbandonRequest(newAbandonRequest(future.getRequestID()));
//              }
                // if (!bindOrStartTLSInProgress.get()) {
                // sendAbandonRequest(newAbandonRequest(future.getRequestID()));
                // }
            }
        }
        return delay;
@@ -813,22 +781,20 @@
        bindOrStartTLSInProgress.set(state);
    }
    void startTLS(final SSLContext sslContext, final List<String> protocols,
            final List<String> cipherSuites, final CompletionHandler<SSLEngine> completionHandler)
            throws IOException {
    void startTLS(final SSLContext sslContext, final List<String> protocols, final List<String> cipherSuites,
            final CompletionHandler<SSLEngine> completionHandler) throws IOException {
        synchronized (stateLock) {
            if (isTLSEnabled()) {
                throw new IllegalStateException("TLS already enabled");
            }
            final SSLEngineConfigurator sslEngineConfigurator =
                    new SSLEngineConfigurator(sslContext, true, false, false);
            final SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(sslContext, true, false,
                    false);
            sslEngineConfigurator.setEnabledProtocols(protocols.isEmpty() ? null : protocols
                    .toArray(new String[protocols.size()]));
            sslEngineConfigurator.setEnabledCipherSuites(cipherSuites.isEmpty() ? null
                    : cipherSuites.toArray(new String[cipherSuites.size()]));
            final SSLFilter sslFilter =
                    new SSLFilter(DUMMY_SSL_ENGINE_CONFIGURATOR, sslEngineConfigurator);
            sslEngineConfigurator.setEnabledCipherSuites(cipherSuites.isEmpty() ? null : cipherSuites
                    .toArray(new String[cipherSuites.size()]));
            final SSLFilter sslFilter = new SSLFilter(DUMMY_SSL_ENGINE_CONFIGURATOR, sslEngineConfigurator);
            installFilter(sslFilter);
            sslFilter.handshake(connection, completionHandler);
        }
@@ -837,16 +803,14 @@
    private ErrorResultException adaptRequestIOException(final IOException e) {
        // FIXME: what other sort of IOExceptions can be thrown?
        // FIXME: Is this the best result code?
        final Result errorResult =
                Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
        final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
        connectionErrorOccurred(errorResult);
        return newErrorResult(errorResult);
    }
    private void checkBindOrStartTLSInProgress() throws ErrorResultException {
        if (bindOrStartTLSInProgress.get()) {
            throw newErrorResult(ResultCode.OPERATIONS_ERROR,
                    "Bind or Start TLS operation in progress");
            throw newErrorResult(ResultCode.OPERATIONS_ERROR, "Bind or Start TLS operation in progress");
        }
    }
@@ -861,8 +825,7 @@
                 * this could be misinterpreted as a genuine authentication
                 * failure for subsequent bind requests.
                 */
                throw newErrorResult(ResultCode.CLIENT_SIDE_SERVER_DOWN,
                        "Connection closed by server");
                throw newErrorResult(ResultCode.CLIENT_SIDE_SERVER_DOWN, "Connection closed by server");
            } else {
                throw newErrorResult(connectionInvalidReason);
            }
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
@@ -27,13 +27,6 @@
package org.forgerock.opendj.grizzly;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_CONNECT_TIMEOUT;
import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.DEFAULT_TRANSPORT;
import static org.forgerock.opendj.grizzly.GrizzlyUtils.buildFilterChain;
import static org.forgerock.opendj.grizzly.GrizzlyUtils.configureConnection;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.TimeoutChecker.TIMEOUT_CHECKER;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutionException;
@@ -46,16 +39,18 @@
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.FutureResultImpl;
import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.TimeoutChecker;
import org.forgerock.opendj.ldap.TimeoutEventListener;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.spi.LDAPConnectionFactoryImpl;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.SuccessHandler;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.SocketConnectorHandler;
@@ -63,9 +58,15 @@
import org.glassfish.grizzly.nio.transport.TCPNIOConnectorHandler;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import com.forgerock.opendj.util.AsynchronousFutureResult;
import com.forgerock.opendj.util.ReferenceCountedObject;
import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.*;
import static org.forgerock.opendj.grizzly.GrizzlyUtils.*;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.TimeoutChecker.*;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.*;
/**
 * LDAP connection factory implementation using Grizzly for transport.
 */
@@ -79,11 +80,10 @@
    @SuppressWarnings("rawtypes")
    private final class CompletionHandlerAdapter implements
            CompletionHandler<org.glassfish.grizzly.Connection>, TimeoutEventListener {
        private final AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> future;
        private final FutureResultImpl<Connection> future;
        private final long timeoutEndTime;
        private CompletionHandlerAdapter(
                final AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> future) {
        private CompletionHandlerAdapter(final FutureResultImpl<Connection> future) {
            this.future = future;
            final long timeoutMS = getTimeout();
            this.timeoutEndTime = timeoutMS > 0 ? System.currentTimeMillis() + timeoutMS : 0;
@@ -120,37 +120,36 @@
                final StartTLSExtendedRequest startTLS =
                        Requests.newStartTLSExtendedRequest(options.getSSLContext());
                startTLS.addEnabledCipherSuite(options.getEnabledCipherSuites().toArray(
                        new String[options.getEnabledCipherSuites().size()]));
                    new String[options.getEnabledCipherSuites().size()]));
                startTLS.addEnabledProtocol(options.getEnabledProtocols().toArray(
                        new String[options.getEnabledProtocols().size()]));
                final ResultHandler<ExtendedResult> handler = new ResultHandler<ExtendedResult>() {
                    @Override
                    public void handleErrorResult(final ErrorResultException error) {
                        onFailure(connection, error);
                    }
                    new String[options.getEnabledProtocols().size()]));
                connection.extendedRequestAsync(startTLS).onSuccess(new SuccessHandler<ExtendedResult>() {
                    @Override
                    public void handleResult(final ExtendedResult result) {
                        onSuccess(connection);
                    }
                };
                connection.extendedRequestAsync(startTLS, null, handler);
                }).onFailure(new FailureHandler<ErrorResultException>() {
                    @Override
                    public void handleError(final ErrorResultException error) {
                        onFailure(connection, error);
                    }
                });
            } else {
                // Install SSL/TLS layer.
                try {
                    connection.startTLS(options.getSSLContext(), options.getEnabledProtocols(),
                            options.getEnabledCipherSuites(),
                            new EmptyCompletionHandler<SSLEngine>() {
                                @Override
                                public void completed(final SSLEngine result) {
                                    onSuccess(connection);
                                }
                        options.getEnabledCipherSuites(), new EmptyCompletionHandler<SSLEngine>() {
                            @Override
                            public void completed(final SSLEngine result) {
                                onSuccess(connection);
                            }
                                @Override
                                public void failed(final Throwable throwable) {
                                    onFailure(connection, throwable);
                                }
                            });
                            @Override
                            public void failed(final Throwable throwable) {
                                onFailure(connection, throwable);
                            }
                        });
                } catch (final IOException e) {
                    onFailure(connection, e);
                }
@@ -161,7 +160,7 @@
        public void failed(final Throwable throwable) {
            // Adapt and forward.
            timeoutChecker.get().removeListener(this);
            future.handleErrorResult(adaptConnectionException(throwable));
            future.handleError(adaptConnectionException(throwable));
            releaseTransportAndTimeoutChecker();
        }
@@ -197,7 +196,7 @@
        private void onFailure(final GrizzlyLDAPConnection connection, final Throwable t) {
            // Abort connection attempt due to error.
            timeoutChecker.get().removeListener(this);
            future.handleErrorResult(adaptConnectionException(t));
            future.handleError(adaptConnectionException(t));
            connection.close();
        }
@@ -216,7 +215,7 @@
            } else if (timeoutEndTime > currentTime) {
                return timeoutEndTime - currentTime;
            } else {
                future.handleErrorResult(newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR,
                future.handleError(newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR,
                        LDAP_CONNECTION_CONNECT_TIMEOUT.get(getSocketAddress(), getTimeout()).toString()));
                return 0;
            }
@@ -303,21 +302,19 @@
    @Override
    public Connection getConnection() throws ErrorResultException {
        try {
            return getConnectionAsync(null).get();
            return getConnectionAsync().getOrThrow();
        } catch (final InterruptedException e) {
            throw newErrorResult(ResultCode.CLIENT_SIDE_USER_CANCELLED, e);
        }
    }
    @Override
    public FutureResult<Connection> getConnectionAsync(
            final ResultHandler<? super Connection> handler) {
    public Promise<Connection, ErrorResultException> getConnectionAsync() {
        acquireTransportAndTimeoutChecker(); // Protect resources.
        final SocketConnectorHandler connectorHandler =
                TCPNIOConnectorHandler.builder(transport.get()).processor(defaultFilterChain)
                        .build();
        final AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> future =
                new AsynchronousFutureResult<Connection, ResultHandler<? super Connection>>(handler);
        final FutureResultImpl<Connection> future = new FutureResultImpl<Connection>();
        connectorHandler.connect(getSocketAddress(), new CompletionHandlerAdapter(future));
        return future;
    }
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2012-2013 ForgeRock AS.
 *      Portions copyright 2012-2014 ForgeRock AS.
 */
package org.forgerock.opendj.grizzly;
@@ -207,7 +207,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            handleResult(error.getResult());
        }
@@ -224,7 +224,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            final Result result = error.getResult();
            if (result instanceof BindResult) {
                handleResult((BindResult) result);
@@ -458,7 +458,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            final Result result = error.getResult();
            if (result instanceof CompareResult) {
                handleResult((CompareResult) result);
@@ -483,7 +483,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            handleResult(error.getResult());
        }
@@ -500,7 +500,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            final Result result = error.getResult();
            if (result instanceof ExtendedResult) {
                handleResult((ExtendedResult) result);
@@ -540,7 +540,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            handleResult(error.getResult());
        }
@@ -557,7 +557,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            handleResult(error.getResult());
        }
@@ -586,7 +586,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            handleResult(error.getResult());
        }
@@ -779,7 +779,7 @@
            if (clientContext != null) {
                final ServerConnection<Integer> conn = clientContext.getServerConnection();
                final SearchHandler handler = new SearchHandler(clientContext, messageID);
                conn.handleSearch(messageID, request, handler, handler);
                conn.handleSearch(messageID, request, handler, handler, handler);
            }
        }
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
@@ -22,20 +22,11 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS
 *      Portions copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.grizzly;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.*;
import static org.testng.Assert.*;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.concurrent.Callable;
@@ -83,6 +74,10 @@
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaBuilder;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.forgerock.util.promise.SuccessHandler;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.annotations.AfterClass;
@@ -90,41 +85,24 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.forgerock.opendj.util.CompletedFutureResult;
import static org.fest.assertions.Assertions.*;
import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.FutureResultWrapper.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.testng.Assert.*;
/**
 * Tests the {@code ConnectionFactory} classes.
 */
@SuppressWarnings("javadoc")
public class ConnectionFactoryTestCase extends SdkTestCase {
    // Test timeout in ms for tests which need to wait for network events.
    /** Test timeout in ms for tests which need to wait for network events. */
    private static final long TEST_TIMEOUT = 30L;
    private static final long TEST_TIMEOUT_MS = TEST_TIMEOUT * 1000L;
    class MyResultHandler implements ResultHandler<Connection> {
        // latch.
        private final CountDownLatch latch;
        // invalid flag.
        private volatile ErrorResultException error;
        MyResultHandler(final CountDownLatch latch) {
            this.latch = latch;
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
            // came here.
            this.error = error;
            latch.countDown();
        }
        @Override
        public void handleResult(final Connection con) {
            con.close();
            latch.countDown();
        }
    }
    /**
     * Ensures that the LDAP Server is running.
     *
@@ -164,7 +142,7 @@
        // HeartBeatConnectionFactory
        // Use custom search request.
        SearchRequest request =
                Requests.newSearchRequest("uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT,
            Requests.newSearchRequest("uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT,
                        "objectclass=*", "cn");
        InetSocketAddress serverAddress = getServerSocketAddress();
@@ -284,11 +262,11 @@
     */
    @Test(dataProvider = "connectionFactories", timeOut = TEST_TIMEOUT_MS)
    public void testBlockingFutureNoHandler(ConnectionFactory factory) throws Exception {
        final FutureResult<Connection> future = factory.getConnectionAsync(null);
        final Connection con = future.get();
        final Promise<? extends Connection, ErrorResultException> promise = factory.getConnectionAsync();
        final Connection con = promise.get();
        // quickly check if it is a valid connection.
        // Don't use a result handler.
        assertNotNull(con.readEntryAsync(DN.rootDN(), null, null).get());
        assertNotNull(con.readEntryAsync(DN.rootDN(), null).getOrThrow());
        con.close();
    }
@@ -300,16 +278,26 @@
    @Test(dataProvider = "connectionFactories", timeOut = TEST_TIMEOUT_MS)
    public void testNonBlockingFutureWithHandler(ConnectionFactory factory) throws Exception {
        // Use the handler to get the result asynchronously.
        final CountDownLatch latch = new CountDownLatch(1);
        final MyResultHandler handler = new MyResultHandler(latch);
        factory.getConnectionAsync(handler);
        final PromiseImpl<Connection, ErrorResultException> promise = PromiseImpl.create();
        factory.getConnectionAsync().onSuccess(new SuccessHandler<Connection>() {
            @Override
            public void handleResult(Connection con) {
                con.close();
                promise.handleResult(con);
            }
        }).onFailure(new FailureHandler<ErrorResultException>() {
            @Override
            public void handleError(ErrorResultException error) {
                promise.handleError(error);
            }
        });
        // Since we don't have anything to do, we would rather
        // be notified by the latch when the other thread calls our handler.
        latch.await(); // should do a timed wait rather?
        if (handler.error != null) {
            throw handler.error;
        }
        // be notified by the promise when the other thread calls our handler.
        promise.getOrThrow(); // should do a timed wait rather?
    }
    /**
@@ -379,7 +367,6 @@
     * @throws Exception
     *             If an unexpected exception occurred.
     */
    @SuppressWarnings("unchecked")
    @Test
    public void testConnectionPoolClose() throws Exception {
        // We'll use a pool of 4 connections.
@@ -392,39 +379,30 @@
        // Mock underlying connection factory which always succeeds.
        final ConnectionFactory mockFactory = mock(ConnectionFactory.class);
        when(mockFactory.getConnectionAsync(any(ResultHandler.class))).thenAnswer(
                new Answer<FutureResult<Connection>>() {
        when(mockFactory.getConnectionAsync()).thenAnswer(new Answer<FutureResult<Connection>>() {
            @Override
            public FutureResult<Connection> answer(InvocationOnMock invocation) throws Throwable {
                // Update state.
                final int connectionID = realConnectionCount.getAndIncrement();
                realConnectionIsClosed[connectionID] = false;
                // Mock connection decrements counter on close.
                Connection mockConnection = mock(Connection.class);
                doAnswer(new Answer<Void>() {
                    @Override
                    public FutureResult<Connection> answer(InvocationOnMock invocation)
                            throws Throwable {
                        // Update state.
                        final int connectionID = realConnectionCount.getAndIncrement();
                        realConnectionIsClosed[connectionID] = false;
                        // Mock connection decrements counter on close.
                        Connection mockConnection = mock(Connection.class);
                        doAnswer(new Answer<Void>() {
                            @Override
                            public Void answer(InvocationOnMock invocation) throws Throwable {
                                realConnectionCount.decrementAndGet();
                                realConnectionIsClosed[connectionID] = true;
                                return null;
                            }
                        }).when(mockConnection).close();
                        when(mockConnection.isValid()).thenReturn(true);
                        when(mockConnection.toString()).thenReturn(
                                "Mock connection " + connectionID);
                        // Execute handler and return future.
                        ResultHandler<? super Connection> handler =
                                (ResultHandler<? super Connection>) invocation.getArguments()[0];
                        if (handler != null) {
                            handler.handleResult(mockConnection);
                        }
                        return new CompletedFutureResult<Connection>(mockConnection);
                    public Void answer(InvocationOnMock invocation) throws Throwable {
                        realConnectionCount.decrementAndGet();
                        realConnectionIsClosed[connectionID] = true;
                        return null;
                    }
                });
                }).when(mockConnection).close();
                when(mockConnection.isValid()).thenReturn(true);
                when(mockConnection.toString()).thenReturn("Mock connection " + connectionID);
                return newSuccessfulFutureResult(mockConnection);
            }
        });
        ConnectionPool pool = Connections.newFixedConnectionPool(mockFactory, size);
        Connection[] pooledConnections = new Connection[size];
@@ -483,10 +461,10 @@
    }
    private static final class CloseNotify {
        private boolean closeOnAccept;
        private boolean doBindFirst;
        private boolean useEventListener;
        private boolean sendDisconnectNotification;
        private final boolean closeOnAccept;
        private final boolean doBindFirst;
        private final boolean useEventListener;
        private final boolean sendDisconnectNotification;
        private CloseNotify(boolean closeOnAccept, boolean doBindFirst, boolean useEventListener,
                boolean sendDisconnectNotification) {
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
@@ -25,20 +25,10 @@
 */
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.TestCaseUtils.findFreeSocketAddress;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.opendj.ldap.Connection;
@@ -69,12 +59,23 @@
import org.forgerock.opendj.ldap.requests.UnbindRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.NeverThrowsException;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.Stubber;
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.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
 * Tests the {@link LDAPConnectionFactory} class.
 */
@@ -121,21 +122,22 @@
    @Test(description = "OPENDJ-1197")
    public void testClientSideConnectTimeout() throws Exception {
        // Use an non-local unreachable network address.
        final ConnectionFactory factory =
                new LDAPConnectionFactory("10.20.30.40", 1389, new LDAPOptions().setConnectTimeout(
                        1, TimeUnit.MILLISECONDS));
        final ConnectionFactory factory = new LDAPConnectionFactory("10.20.30.40", 1389,
                new LDAPOptions().setConnectTimeout(1, TimeUnit.MILLISECONDS));
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                final ResultHandler<Connection> handler = mock(ResultHandler.class);
                final FutureResult<Connection> future = factory.getConnectionAsync(handler);
                final PromiseImpl<ErrorResultException, NeverThrowsException> promise = PromiseImpl.create();
                final Promise<? extends Connection, ErrorResultException> future = factory.getConnectionAsync();
                future.onFailure(getFailureHandler(promise));
                ConnectionException e = (ConnectionException) promise.getOrThrow(TEST_TIMEOUT, TimeUnit.SECONDS);
                assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_CONNECT_ERROR);
                // Wait for the connect to timeout.
                try {
                    future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
                    future.getOrThrow(TEST_TIMEOUT, TimeUnit.SECONDS);
                    fail("The connect request succeeded unexpectedly");
                } catch (ConnectionException e) {
                    assertThat(e.getResult().getResultCode()).isEqualTo(
                            ResultCode.CLIENT_SIDE_CONNECT_ERROR);
                    verify(handler).handleErrorResult(same(e));
                } catch (ConnectionException ce) {
                    assertThat(ce.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_CONNECT_ERROR);
                }
            }
        } finally {
@@ -161,32 +163,32 @@
            waitForConnect();
            final MockConnectionEventListener listener = new MockConnectionEventListener();
            connection.addConnectionEventListener(listener);
            final ResultHandler<BindResult> handler = mock(ResultHandler.class);
            final FutureResult<BindResult> future =
                    connection.bindAsync(newSimpleBindRequest(), null, handler);
            final PromiseImpl<ErrorResultException, NeverThrowsException> promise = PromiseImpl.create();
            final FutureResult<BindResult> future = connection.bindAsync(newSimpleBindRequest());
            future.onFailure(getFailureHandler(promise));
            waitForBind();
            TimeoutResultException e = (TimeoutResultException) promise.getOrThrow(TEST_TIMEOUT, TimeUnit.SECONDS);
            verifyResultCodeIsClientSideTimeout(e);
            // Wait for the request to timeout.
            try {
                future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
                future.getOrThrow(TEST_TIMEOUT, TimeUnit.SECONDS);
                fail("The bind request succeeded unexpectedly");
            } catch (TimeoutResultException e) {
                verifyResultCodeIsClientSideTimeout(e);
                verify(handler).handleErrorResult(same(e));
                /*
                 * The connection should no longer be valid, the event listener
                 * should have been notified, but no abandon should have been
                 * sent.
                 */
                listener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
                assertThat(connection.isValid()).isFalse();
                verifyResultCodeIsClientSideTimeout(listener.getError());
                connection.close();
                waitForClose();
                verifyNoAbandonSent();
            } catch (TimeoutResultException te) {
                verifyResultCodeIsClientSideTimeout(te);
            }
            /*
             * The connection should no longer be valid, the event listener
             * should have been notified, but no abandon should have been sent.
             */
            listener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
            assertThat(connection.isValid()).isFalse();
            verifyResultCodeIsClientSideTimeout(listener.getError());
            connection.close();
            waitForClose();
            verifyNoAbandonSent();
        } finally {
            connection.close();
        }
@@ -211,39 +213,41 @@
                connection.addConnectionEventListener(listener);
                // Now bind with timeout.
                final ResultHandler<BindResult> handler = mock(ResultHandler.class);
                final FutureResult<BindResult> future =
                        connection.bindAsync(newSimpleBindRequest(), null, handler);
                final PromiseImpl<ErrorResultException, NeverThrowsException> promise = PromiseImpl.create();
                final FutureResult<BindResult> future = connection.bindAsync(newSimpleBindRequest());
                future.onFailure(getFailureHandler(promise));
                waitForBind();
                // Wait for the request to timeout.
                try {
                    future.get(5, TimeUnit.SECONDS);
                    fail("The bind request succeeded unexpectedly");
                } catch (TimeoutException e) {
                    fail("The bind request future get timed out");
                } catch (TimeoutResultException e) {
                    verifyResultCodeIsClientSideTimeout(e);
                    verify(handler).handleErrorResult(same(e));
                // Wait for the request to timeout and check the handler was invoked.
                TimeoutResultException e = (TimeoutResultException) promise.getOrThrow(5, TimeUnit.SECONDS);
                verifyResultCodeIsClientSideTimeout(e);
                    /*
                     * The connection should no longer be valid, the event
                     * listener should have been notified, but no abandon should
                     * have been sent.
                     */
                    listener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
                    assertThat(connection.isValid()).isFalse();
                    verifyResultCodeIsClientSideTimeout(listener.getError());
                    connection.close();
                    waitForClose();
                    verifyNoAbandonSent();
                // Now check the future was completed as expected.
                try {
                    future.getOrThrow(5, TimeUnit.SECONDS);
                    fail("The bind request succeeded unexpectedly");
                } catch (TimeoutResultException te) {
                    verifyResultCodeIsClientSideTimeout(te);
                }
                /*
                 * The connection should no longer be valid, the event listener
                 * should have been notified, but no abandon should have been
                 * sent.
                 */
                listener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
                assertThat(connection.isValid()).isFalse();
                verifyResultCodeIsClientSideTimeout(listener.getError());
                connection.close();
                waitForClose();
                verifyNoAbandonSent();
            } finally {
                connection.close();
            }
        }
    }
    /**
     * Unit test for OPENDJ-1247: a locally timed out request which is not a
     * bind or startTLS should result in a client side timeout error, but the
@@ -262,30 +266,29 @@
                waitForConnect();
                final ConnectionEventListener listener = mock(ConnectionEventListener.class);
                connection.addConnectionEventListener(listener);
                final ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
                final FutureResult<SearchResultEntry> future =
                        connection.readEntryAsync(DN.valueOf("cn=test"), null, handler);
                final PromiseImpl<ErrorResultException, NeverThrowsException> promise = PromiseImpl.create();
                final FutureResult<SearchResultEntry> future = connection.readEntryAsync(DN.valueOf("cn=test"), null);
                future.onFailure(getFailureHandler(promise));
                waitForSearch();
                ErrorResultException e = promise.getOrThrow(TEST_TIMEOUT, TimeUnit.SECONDS);
                verifyResultCodeIsClientSideTimeout(e);
                // Wait for the request to timeout.
                try {
                    future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
                    future.getOrThrow(TEST_TIMEOUT, TimeUnit.SECONDS);
                    fail("The search request succeeded unexpectedly");
                } catch (TimeoutResultException e) {
                    verifyResultCodeIsClientSideTimeout(e);
                    verify(handler).handleErrorResult(same(e));
                    // The connection should still be valid.
                    assertThat(connection.isValid()).isTrue();
                    verifyZeroInteractions(listener);
                    /*
                     * FIXME: The search should have been abandoned (see comment
                     * in LDAPConnection for explanation).
                     */
                    // waitForAbandon();
                } catch (TimeoutResultException te) {
                    verifyResultCodeIsClientSideTimeout(te);
                }
                // The connection should still be valid.
                assertThat(connection.isValid()).isTrue();
                verifyZeroInteractions(listener);
                /*
                 * FIXME: The search should have been abandoned (see comment in
                 * LDAPConnection for explanation).
                 */
                // waitForAbandon();
            } finally {
                connection.close();
            }
@@ -296,8 +299,7 @@
    public void testCreateLDAPConnectionFactory() throws Exception {
        // test no exception is thrown, which means transport provider is correctly loaded
        InetSocketAddress socketAddress = findFreeSocketAddress();
        LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
                socketAddress.getPort());
        LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(), socketAddress.getPort());
        factory.close();
    }
@@ -314,9 +316,7 @@
    @Test
    public void testCreateLDAPConnectionFactoryWithCustomClassLoader() throws Exception {
        // test no exception is thrown, which means transport provider is correctly loaded
        LDAPOptions options =
                new LDAPOptions().setProviderClassLoader(Thread.currentThread()
                        .getContextClassLoader());
        LDAPOptions options = new LDAPOptions().setProviderClassLoader(Thread.currentThread().getContextClassLoader());
        InetSocketAddress socketAddress = findFreeSocketAddress();
        LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
                socketAddress.getPort(), options);
@@ -367,6 +367,16 @@
        }
    }
    private FailureHandler<ErrorResultException> getFailureHandler(
            final PromiseImpl<ErrorResultException, NeverThrowsException> promise) {
        return new FailureHandler<ErrorResultException>() {
            @Override
            public void handleError(ErrorResultException error) {
                promise.handleResult(error);
            }
        };
    }
    private Stubber notifyEvent(final Semaphore latch) {
        return doAnswer(new Answer<Void>() {
            @Override
@@ -394,9 +404,8 @@
    }
    private void registerSearchEvent() {
        notifyEvent(searchLatch).when(serverConnection).handleSearch(any(Integer.class),
                any(SearchRequest.class), any(IntermediateResponseHandler.class),
                any(SearchResultHandler.class));
        notifyEvent(searchLatch).when(serverConnection).handleSearch(any(Integer.class), any(SearchRequest.class),
            any(IntermediateResponseHandler.class), any(SearchResultHandler.class), any(ResultHandler.class));
    }
    private void resetState() {
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
@@ -49,6 +49,7 @@
import org.forgerock.opendj.ldap.controls.PersistentSearchRequestControl;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.util.promise.FailureHandler;
import org.mockito.ArgumentCaptor;
import org.testng.annotations.Test;
@@ -100,17 +101,19 @@
            if (isPersistentSearch) {
                request.addControl(PersistentSearchRequestControl.newControl(true, true, true));
            }
            SearchResultHandler handler = mock(SearchResultHandler.class);
            connection.searchAsync(request, null, handler);
            SearchResultHandler searchHandler = mock(SearchResultHandler.class);
            @SuppressWarnings("unchecked")
            FailureHandler<ErrorResultException> failureHandler = mock(FailureHandler.class);
            connection.searchAsync(request, searchHandler).onFailure(failureHandler);
            // Pass in a time which is guaranteed to trigger expiration.
            connection.handleTimeout(System.currentTimeMillis() + 1000000);
            if (isPersistentSearch) {
                verifyZeroInteractions(handler);
                verifyZeroInteractions(searchHandler);
            } else {
                ArgumentCaptor<ErrorResultException> arg =
                        ArgumentCaptor.forClass(ErrorResultException.class);
                verify(handler).handleErrorResult(arg.capture());
                verify(failureHandler).handleError(arg.capture());
                assertThat(arg.getValue()).isInstanceOf(TimeoutResultException.class);
                assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(
                        ResultCode.CLIENT_SIDE_TIMEOUT);
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
@@ -22,15 +22,10 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS.
 *      Portions copyright 2011-2014 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.TestCaseUtils.findFreeSocketAddress;
import static org.mockito.Mockito.mock;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
@@ -43,6 +38,7 @@
import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResultImpl;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
@@ -76,7 +72,10 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.forgerock.opendj.util.AsynchronousFutureResult;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
import static org.mockito.Mockito.mock;
/**
 * Tests the LDAPListener class.
@@ -85,11 +84,8 @@
public class GrizzlyLDAPListenerTestCase extends SdkTestCase {
    private static class MockServerConnection implements ServerConnection<Integer> {
        final AsynchronousFutureResult<Throwable, ResultHandler<Throwable>> connectionError =
                new AsynchronousFutureResult<Throwable, ResultHandler<Throwable>>(null);
        final AsynchronousFutureResult<LDAPClientContext, ResultHandler<LDAPClientContext>> context =
                new AsynchronousFutureResult<LDAPClientContext, ResultHandler<LDAPClientContext>>(
                        null);
        final FutureResultImpl<Throwable> connectionError = new FutureResultImpl<Throwable>();
        final FutureResultImpl<LDAPClientContext> context = new FutureResultImpl<LDAPClientContext>();
        final CountDownLatch isClosed = new CountDownLatch(1);
        MockServerConnection() {
@@ -179,7 +175,7 @@
                final ExtendedRequest<R> request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<R> resultHandler) throws UnsupportedOperationException {
            resultHandler.handleErrorResult(ErrorResultException.newErrorResult(request
            resultHandler.handleError(ErrorResultException.newErrorResult(request
                    .getResultDecoder().newExtendedErrorResult(ResultCode.PROTOCOL_ERROR, "",
                            "Extended operation " + request.getOID() + " not supported")));
        }
@@ -209,8 +205,8 @@
         */
        @Override
        public void handleSearch(final Integer requestContext, final SearchRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final SearchResultHandler resultHandler) throws UnsupportedOperationException {
            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler,
            final ResultHandler<Result> resultHandler) throws UnsupportedOperationException {
            resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
        }
@@ -458,7 +454,7 @@
                        resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS));
                    } catch (final Exception e) {
                        // Unexpected.
                        resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
                        resultHandler.handleError(ErrorResultException.newErrorResult(
                                ResultCode.OTHER,
                                "Unexpected exception when connecting to load balancer", e));
                    }
@@ -615,7 +611,7 @@
                    try {
                        // This is expected to fail.
                        lcf.getConnection().close();
                        resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
                        resultHandler.handleError(ErrorResultException.newErrorResult(
                                ResultCode.OTHER,
                                "Connection to offline server succeeded unexpectedly"));
                    } catch (final ConnectionException ce) {
@@ -627,13 +623,13 @@
                            resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS));
                        } catch (final Exception e) {
                            // Unexpected.
                            resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
                            resultHandler.handleError(ErrorResultException.newErrorResult(
                                    ResultCode.OTHER,
                                    "Unexpected exception when connecting to online server", e));
                        }
                    } catch (final Exception e) {
                        // Unexpected.
                        resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
                        resultHandler.handleError(ErrorResultException.newErrorResult(
                                ResultCode.OTHER,
                                "Unexpected exception when connecting to offline server", e));
                    }
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
@@ -862,16 +862,6 @@
    private static class MySearchResultHandler implements SearchResultHandler {
        @Override
        public void handleErrorResult(ErrorResultException error) {
            // Ignore.
        }
        @Override
        public void handleResult(Result result) {
            // Ignore.
        }
        @Override
        public boolean handleEntry(SearchResultEntry entry) {
            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
            try {
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ProxyBackend.java
@@ -22,12 +22,12 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS
 *      Portions copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.examples;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
@@ -54,8 +54,12 @@
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
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.AsyncFunction;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.SuccessHandler;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.util.Utils.*;
/**
 * A simple proxy back-end which forwards requests to a connection factory using
@@ -75,330 +79,186 @@
 *
 * <pre>
 * final RequestHandlerFactory&lt;LDAPClientContext, RequestContext&gt; proxyFactory =
 *         new RequestHandlerFactory&lt;LDAPClientContext, RequestContext&gt;() {
 *             &#064;Override
 *             public ProxyBackend handleAccept(LDAPClientContext clientContext)
 *                     throws ErrorResultException {
 *                 return new ProxyBackend(factory, bindFactory);
 *             }
 *         };
 *     new RequestHandlerFactory&lt;LDAPClientContext, RequestContext&gt;() {
 *         &#064;Override
 *         public ProxyBackend handleAccept(LDAPClientContext clientContext) throws ErrorResultException {
 *             return new ProxyBackend(factory, bindFactory);
 *         }
 *     };
 * final ServerConnectionFactory&lt;LDAPClientContext, Integer&gt; connectionHandler = Connections
 *         .newServerConnectionFactory(proxyFactory);
 *     .newServerConnectionFactory(proxyFactory);
 * </pre>
 */
final class ProxyBackend implements RequestHandler<RequestContext> {
    private abstract class AbstractRequestCompletionHandler<R extends Result, H extends ResultHandler<R>>
            implements ResultHandler<R> {
        final Connection connection;
        final H resultHandler;
        AbstractRequestCompletionHandler(final Connection connection, final H resultHandler) {
            this.connection = connection;
            this.resultHandler = resultHandler;
        }
        @Override
        public final void handleErrorResult(final ErrorResultException error) {
            connection.close();
            resultHandler.handleErrorResult(error);
        }
        @Override
        public final void handleResult(final R result) {
            connection.close();
            resultHandler.handleResult(result);
        }
    }
    private abstract class ConnectionCompletionHandler<R extends Result> implements
            ResultHandler<Connection> {
        private final ResultHandler<R> resultHandler;
        ConnectionCompletionHandler(final ResultHandler<R> resultHandler) {
            this.resultHandler = resultHandler;
        }
        @Override
        public final void handleErrorResult(final ErrorResultException error) {
            resultHandler.handleErrorResult(error);
        }
        @Override
        public abstract void handleResult(Connection connection);
    }
    private final class RequestCompletionHandler<R extends Result> extends
            AbstractRequestCompletionHandler<R, ResultHandler<R>> {
        RequestCompletionHandler(final Connection connection, final ResultHandler<R> resultHandler) {
            super(connection, resultHandler);
        }
    }
    private final class SearchRequestCompletionHandler extends
            AbstractRequestCompletionHandler<Result, SearchResultHandler> implements
            SearchResultHandler {
        SearchRequestCompletionHandler(final Connection connection,
                final SearchResultHandler resultHandler) {
            super(connection, resultHandler);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public final boolean handleEntry(final SearchResultEntry entry) {
            return resultHandler.handleEntry(entry);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public final boolean handleReference(final SearchResultReference reference) {
            return resultHandler.handleReference(reference);
        }
    }
    private final ConnectionFactory bindFactory;
    private final ConnectionFactory factory;
    private volatile ProxiedAuthV2RequestControl proxiedAuthControl = null;
    private volatile ProxiedAuthV2RequestControl proxiedAuthControl;
    ProxyBackend(final ConnectionFactory factory, final ConnectionFactory bindFactory) {
        this.factory = factory;
        this.bindFactory = bindFactory;
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public void handleAdd(final RequestContext requestContext, final AddRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<Result> resultHandler) {
        final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler<Result> resultHandler) {
        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
        addProxiedAuthControl(request);
        final ConnectionCompletionHandler<Result> outerHandler =
                new ConnectionCompletionHandler<Result>(resultHandler) {
                    @Override
                    public void handleResult(final Connection connection) {
                        final RequestCompletionHandler<Result> innerHandler =
                                new RequestCompletionHandler<Result>(connection, resultHandler);
                        connection.addAsync(request, intermediateResponseHandler, innerHandler);
                    }
                };
        factory.getConnectionAsync(outerHandler);
        factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, ErrorResultException>() {
            @Override
            public Promise<Result, ErrorResultException> apply(Connection connection) throws ErrorResultException {
                connectionHolder.set(connection);
                return connection.addAsync(request, intermediateResponseHandler);
            }
        }).onSuccess(resultHandler).onFailure(resultHandler).thenAlways(close(connectionHolder));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public void handleBind(final RequestContext requestContext, final int version,
            final BindRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<BindResult> resultHandler) {
    public void handleBind(final RequestContext requestContext, final int version, final BindRequest request,
        final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler<BindResult> resultHandler) {
        if (request.getAuthenticationType() != BindRequest.AUTHENTICATION_TYPE_SIMPLE) {
            // TODO: SASL authentication not implemented.
            resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
            resultHandler.handleError(newErrorResult(ResultCode.PROTOCOL_ERROR,
                    "non-SIMPLE authentication not supported: " + request.getAuthenticationType()));
        } else {
            // Authenticate using a separate bind connection pool, because
            // we don't want to change the state of the pooled connection.
            final ConnectionCompletionHandler<BindResult> outerHandler =
                    new ConnectionCompletionHandler<BindResult>(resultHandler) {
                        @Override
                        public void handleResult(final Connection connection) {
                            final ResultHandler<BindResult> innerHandler =
                                    new ResultHandler<BindResult>() {
                                        @Override
                                        public final void handleErrorResult(
                                                final ErrorResultException error) {
                                            connection.close();
                                            resultHandler.handleErrorResult(error);
                                        }
                                        @Override
                                        public final void handleResult(final BindResult result) {
                                            connection.close();
                                            proxiedAuthControl =
                                                    ProxiedAuthV2RequestControl.newControl("dn:"
                                                            + request.getName());
                                            resultHandler.handleResult(result);
                                        }
                                    };
                            connection
                                    .bindAsync(request, intermediateResponseHandler, innerHandler);
                        }
                    };
            final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
            proxiedAuthControl = null;
            bindFactory.getConnectionAsync(outerHandler);
            bindFactory.getConnectionAsync()
                    .thenAsync(new AsyncFunction<Connection, BindResult, ErrorResultException>() {
                        @Override
                        public Promise<BindResult, ErrorResultException> apply(Connection connection)
                                throws ErrorResultException {
                            connectionHolder.set(connection);
                            return connection.bindAsync(request, intermediateResponseHandler);
                        }
                    }).onSuccess(new SuccessHandler<BindResult>() {
                        @Override
                        public final void handleResult(final BindResult result) {
                            proxiedAuthControl = ProxiedAuthV2RequestControl.newControl("dn:" + request.getName());
                            resultHandler.handleResult(result);
                        }
                    }).onFailure(resultHandler).thenAlways(close(connectionHolder));
        }
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public void handleCompare(final RequestContext requestContext, final CompareRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<CompareResult> resultHandler) {
        addProxiedAuthControl(request);
        final ConnectionCompletionHandler<CompareResult> outerHandler =
                new ConnectionCompletionHandler<CompareResult>(resultHandler) {
                    @Override
                    public void handleResult(final Connection connection) {
                        final RequestCompletionHandler<CompareResult> innerHandler =
                                new RequestCompletionHandler<CompareResult>(connection,
                                        resultHandler);
                        connection.compareAsync(request, intermediateResponseHandler, innerHandler);
                    }
                };
        factory.getConnectionAsync(outerHandler);
        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
        factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, CompareResult, ErrorResultException>() {
            @Override
            public Promise<CompareResult, ErrorResultException> apply(Connection connection)
                    throws ErrorResultException {
                connectionHolder.set(connection);
                return connection.compareAsync(request, intermediateResponseHandler);
            }
        }).onSuccess(resultHandler).onFailure(resultHandler).thenAlways(close(connectionHolder));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler<Result> resultHandler) {
        addProxiedAuthControl(request);
        final ConnectionCompletionHandler<Result> outerHandler =
                new ConnectionCompletionHandler<Result>(resultHandler) {
                    @Override
                    public void handleResult(final Connection connection) {
                        final RequestCompletionHandler<Result> innerHandler =
                                new RequestCompletionHandler<Result>(connection, resultHandler);
                        connection.deleteAsync(request, intermediateResponseHandler, innerHandler);
                    }
                };
        factory.getConnectionAsync(outerHandler);
        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
        factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, ErrorResultException>() {
            @Override
            public Promise<Result, ErrorResultException> apply(Connection connection) throws ErrorResultException {
                connectionHolder.set(connection);
                return connection.deleteAsync(request, intermediateResponseHandler);
            }
        }).onSuccess(resultHandler).onFailure(resultHandler).thenAlways(close(connectionHolder));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public <R extends ExtendedResult> void handleExtendedRequest(
            final RequestContext requestContext, final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<R> resultHandler) {
        if (request.getOID().equals(CancelExtendedRequest.OID)) {
    public <R extends ExtendedResult> void handleExtendedRequest(final RequestContext requestContext,
        final ExtendedRequest<R> request, final IntermediateResponseHandler intermediateResponseHandler,
        final ResultHandler<R> resultHandler) {
        if (CancelExtendedRequest.OID.equals(request.getOID())) {
            // TODO: not implemented.
            resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
                    "Cancel extended request operation not supported"));
        } else if (request.getOID().equals(StartTLSExtendedRequest.OID)) {
            resultHandler.handleError(newErrorResult(ResultCode.PROTOCOL_ERROR,
                "Cancel extended request operation not supported"));
        } else if (StartTLSExtendedRequest.OID.equals(request.getOID())) {
            // TODO: not implemented.
            resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
                    "StartTLS extended request operation not supported"));
            resultHandler.handleError(newErrorResult(ResultCode.PROTOCOL_ERROR,
                "StartTLS extended request operation not supported"));
        } else {
            // Forward all other extended operations.
            addProxiedAuthControl(request);
            final ConnectionCompletionHandler<R> outerHandler =
                    new ConnectionCompletionHandler<R>(resultHandler) {
                        @Override
                        public void handleResult(final Connection connection) {
                            final RequestCompletionHandler<R> innerHandler =
                                    new RequestCompletionHandler<R>(connection, resultHandler);
                            connection.extendedRequestAsync(request, intermediateResponseHandler,
                                    innerHandler);
                        }
                    };
            factory.getConnectionAsync(outerHandler);
            final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
            factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, R, ErrorResultException>() {
                @Override
                public Promise<R, ErrorResultException> apply(Connection connection) throws ErrorResultException {
                    connectionHolder.set(connection);
                    return connection.extendedRequestAsync(request, intermediateResponseHandler);
                }
            }).onSuccess(resultHandler).onFailure(resultHandler)
                .thenAlways(close(connectionHolder));
        }
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public void handleModify(final RequestContext requestContext, final ModifyRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<Result> resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler<Result> resultHandler) {
        addProxiedAuthControl(request);
        final ConnectionCompletionHandler<Result> outerHandler =
                new ConnectionCompletionHandler<Result>(resultHandler) {
                    @Override
                    public void handleResult(final Connection connection) {
                        final RequestCompletionHandler<Result> innerHandler =
                                new RequestCompletionHandler<Result>(connection, resultHandler);
                        connection.modifyAsync(request, intermediateResponseHandler, innerHandler);
                    }
                };
        factory.getConnectionAsync(outerHandler);
        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
        factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, ErrorResultException>() {
            @Override
            public Promise<Result, ErrorResultException> apply(Connection connection) throws ErrorResultException {
                connectionHolder.set(connection);
                return connection.modifyAsync(request, intermediateResponseHandler);
            }
        }).onSuccess(resultHandler).onFailure(resultHandler).thenAlways(close(connectionHolder));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public void handleModifyDN(final RequestContext requestContext, final ModifyDNRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final ResultHandler<Result> resultHandler) {
        final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler<Result> resultHandler) {
        addProxiedAuthControl(request);
        final ConnectionCompletionHandler<Result> outerHandler =
                new ConnectionCompletionHandler<Result>(resultHandler) {
                    @Override
                    public void handleResult(final Connection connection) {
                        final RequestCompletionHandler<Result> innerHandler =
                                new RequestCompletionHandler<Result>(connection, resultHandler);
                        connection
                                .modifyDNAsync(request, intermediateResponseHandler, innerHandler);
                    }
                };
        factory.getConnectionAsync(outerHandler);
        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
        factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, ErrorResultException>() {
            @Override
            public Promise<Result, ErrorResultException> apply(Connection connection) throws ErrorResultException {
                connectionHolder.set(connection);
                return connection.modifyDNAsync(request, intermediateResponseHandler);
            }
        }).onSuccess(resultHandler).onFailure(resultHandler).thenAlways(close(connectionHolder));
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public void handleSearch(final RequestContext requestContext, final SearchRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final SearchResultHandler resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler,
            final ResultHandler<Result> resultHandler) {
        addProxiedAuthControl(request);
        final ConnectionCompletionHandler<Result> outerHandler =
                new ConnectionCompletionHandler<Result>(resultHandler) {
                    @Override
                    public void handleResult(final Connection connection) {
                        final SearchRequestCompletionHandler innerHandler =
                                new SearchRequestCompletionHandler(connection, resultHandler);
                        connection.searchAsync(request, intermediateResponseHandler, innerHandler);
                    }
                };
        factory.getConnectionAsync(outerHandler);
        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
        factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, ErrorResultException>() {
            @Override
            public Promise<Result, ErrorResultException> apply(Connection connection) throws ErrorResultException {
                connectionHolder.set(connection);
                return connection.searchAsync(request, intermediateResponseHandler, entryHandler);
            }
        }).onSuccess(resultHandler).onFailure(resultHandler).thenAlways(close(connectionHolder));
    }
    private void addProxiedAuthControl(final Request request) {
@@ -407,4 +267,13 @@
            request.addControl(control);
        }
    }
    private Runnable close(final AtomicReference<Connection> c) {
        return new Runnable() {
            @Override
            public void run() {
                closeSilently(c.get());
            }
        };
    }
}
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2013 ForgeRock AS
 *      Portions copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.examples;
@@ -109,11 +109,11 @@
public final class RewriterProxy {
    private static final class Rewriter implements RequestHandler<RequestContext> {
        // This example hard codes the attribute...
        /** This example hard codes the attribute... */
        private static final String CLIENT_ATTRIBUTE = "fullname";
        private static final String SERVER_ATTRIBUTE = "cn";
        // ...and DN rewriting configuration.
        /** ...and DN rewriting configuration. */
        private static final String CLIENT_SUFFIX = "o=example";
        private static final String SERVER_SUFFIX = "dc=example,dc=com";
@@ -122,7 +122,7 @@
        private final AttributeDescription serverAttributeDescription = AttributeDescription
                .valueOf(SERVER_ATTRIBUTE);
        // Next request handler in the chain.
        /** Next request handler in the chain. */
        private final RequestHandler<RequestContext> nextHandler;
        private Rewriter(final RequestHandler<RequestContext> nextHandler) {
@@ -191,32 +191,20 @@
        @Override
        public void handleSearch(final RequestContext requestContext, final SearchRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final SearchResultHandler resultHandler) {
            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler,
            final ResultHandler<Result> resultHandler) {
            nextHandler.handleSearch(requestContext, rewrite(request), intermediateResponseHandler,
                    new SearchResultHandler() {
                new SearchResultHandler() {
                    @Override
                    public boolean handleReference(SearchResultReference reference) {
                        return entryHandler.handleReference(reference);
                    }
                        @Override
                        public boolean handleEntry(final SearchResultEntry entry) {
                            return resultHandler.handleEntry(rewrite(entry));
                        }
                        @Override
                        public void handleErrorResult(final ErrorResultException error) {
                            resultHandler.handleErrorResult(error);
                        }
                        @Override
                        public boolean handleReference(final SearchResultReference reference) {
                            return resultHandler.handleReference(reference);
                        }
                        @Override
                        public void handleResult(final Result result) {
                            resultHandler.handleResult(result);
                        }
                    });
                    @Override
                    public boolean handleEntry(SearchResultEntry entry) {
                        return entryHandler.handleEntry(rewrite(entry));
                    }
                }, resultHandler);
        }
        private AddRequest rewrite(final AddRequest request) {
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2012 ForgeRock AS
 *      Portions copyright 2011-2014 ForgeRock AS
 */
package org.forgerock.opendj.examples;
@@ -34,21 +34,23 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.FutureResultWrapper;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.CancelExtendedRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
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.LDIFEntryWriter;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.SuccessHandler;
/**
 * An example client application which searches a Directory Server using the
@@ -60,79 +62,32 @@
 * </pre>
 */
public final class SearchAsync {
    private static final class BindResultHandlerImpl implements ResultHandler<BindResult> {
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleErrorResult(final ErrorResultException error) {
            System.err.println(error.getMessage());
            resultCode = error.getResult().getResultCode().intValue();
            COMPLETION_LATCH.countDown();
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleResult(final BindResult result) {
            // Bind succeeded: initiate search.
            final SearchRequest request =
                    Requests.newSearchRequest(baseDN, scope, filter, attributes);
            final FutureResult<Result> futureResult =
                    connection.searchAsync(request, null, new SearchResultHandlerImpl());
            requestID = futureResult.getRequestID();
        }
    }
    private static final class ConnectResultHandlerImpl implements ResultHandler<Connection> {
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleErrorResult(final ErrorResultException error) {
            System.err.println(error.getMessage());
            resultCode = error.getResult().getResultCode().intValue();
            COMPLETION_LATCH.countDown();
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleResult(final Connection connection) {
            // Connect succeeded: save connection and initiate bind.
            SearchAsync.connection = connection;
            final BindRequest request =
                    Requests.newSimpleBindRequest(userName, password.toCharArray());
            connection.bindAsync(request, null, new BindResultHandlerImpl());
        }
    }
    // --- JCite search result handler ---
    private static final class SearchResultHandlerImpl implements SearchResultHandler {
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public synchronized boolean handleEntry(final SearchResultEntry entry) {
            try {
                if (entryCount < 10) {
                    WRITER.writeComment("Search result entry: "
                            + entry.getName().toString());
                    WRITER.writeComment("Search result entry: " + entry.getName().toString());
                    WRITER.writeEntry(entry);
                    ++entryCount;
                } else { // Cancel the search.
                    CancelExtendedRequest request =
                            Requests.newCancelExtendedRequest(requestID);
                    connection.extendedRequestAsync(request, null,
                            new CancelResultHandlerImpl());
                    CancelExtendedRequest request = Requests.newCancelExtendedRequest(requestID);
                    connection.extendedRequestAsync(request).onSuccess(new SuccessHandler<ExtendedResult>() {
                        @Override
                        public void handleResult(ExtendedResult result) {
                            System.err.println("Cancel request succeeded");
                            CANCEL_LATCH.countDown();
                        }
                    }).onFailure(new FailureHandler<ErrorResultException>() {
                        @Override
                        public void handleError(ErrorResultException error) {
                            System.err.println("Cancel request failed with result code: "
                                + error.getResult().getResultCode().intValue());
                            CANCEL_LATCH.countDown();
                        }
                    });
                    return false;
                }
            } catch (final IOException e) {
@@ -144,25 +99,11 @@
            return true;
        }
        /**
         * {@inheritDoc}
         */
        /** {@inheritDoc} */
        @Override
        public void handleErrorResult(final ErrorResultException error) {
            System.err.println(error.getMessage());
            resultCode = error.getResult().getResultCode().intValue();
            COMPLETION_LATCH.countDown();
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public synchronized boolean handleReference(
                final SearchResultReference reference) {
        public synchronized boolean handleReference(final SearchResultReference reference) {
            try {
                WRITER.writeComment("Search result reference: "
                        + reference.getURIs().toString());
                WRITER.writeComment("Search result reference: " + reference.getURIs().toString());
            } catch (final IOException e) {
                System.err.println(e.getMessage());
                resultCode = ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue();
@@ -172,18 +113,10 @@
            return true;
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleResult(final Result result) {
            resultCode = result.getResultCode().intValue();
            COMPLETION_LATCH.countDown();
        }
    }
    // --- JCite search result handler ---
    // --- JCite decl1 ---
    private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
    private static final CountDownLatch CANCEL_LATCH = new CountDownLatch(1);
@@ -203,26 +136,6 @@
    static int entryCount = 0;
    // --- JCite decl2 ---
    // --- JCite cancel result handler ---
    private static final class CancelResultHandlerImpl
            implements ResultHandler<ExtendedResult> {
        @Override
        public void handleErrorResult(final ErrorResultException error) {
            System.err.println("Cancel request failed with result code: "
                    + error.getResult().getResultCode().intValue());
            CANCEL_LATCH.countDown();
        }
        @Override
        public void handleResult(final ExtendedResult result) {
            System.err.println("Cancel request succeeded");
            CANCEL_LATCH.countDown();
        }
    }
    // --- JCite cancel result handler ---
    /**
     * Main method.
     *
@@ -233,8 +146,7 @@
     */
    public static void main(final String[] args) {
        if (args.length < 7) {
            System.err.println("Usage: host port username password baseDN scope "
                    + "filter [attribute ...]");
            System.err.println("Usage: host port username password baseDN scope " + "filter [attribute ...]");
            System.exit(1);
        }
@@ -266,9 +178,38 @@
            return;
        }
        // Initiate the asynchronous connect, bind, and search.
        final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
        factory.getConnectionAsync(new ConnectResultHandlerImpl());
        factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, BindResult, ErrorResultException>() {
            @Override
            public Promise<BindResult, ErrorResultException> apply(Connection connection) throws ErrorResultException {
                SearchAsync.connection = connection;
                return connection.bindAsync(Requests.newSimpleBindRequest(userName, password.toCharArray()));
            }
        }).thenAsync(new AsyncFunction<BindResult, Result, ErrorResultException>() {
            @Override
            public Promise<Result, ErrorResultException> apply(BindResult result) throws ErrorResultException {
                FutureResult<Result> future = FutureResultWrapper.asFutureResult(connection.searchAsync(
                        Requests.newSearchRequest(baseDN, scope, filter, attributes), new SearchResultHandlerImpl()));
                requestID = future.getRequestID();
                return future;
            }
        }).onSuccess(new SuccessHandler<Result>() {
            @Override
            public void handleResult(Result result) {
                resultCode = result.getResultCode().intValue();
                COMPLETION_LATCH.countDown();
            }
        }).onFailure(new FailureHandler<ErrorResultException>() {
            @Override
            public void handleError(ErrorResultException error) {
                System.err.println(error.getMessage());
                resultCode = error.getResult().getResultCode().intValue();
                COMPLETION_LATCH.countDown();
            }
        });
        // Await completion.
        try {
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AddRate.java
@@ -26,10 +26,6 @@
package com.forgerock.opendj.ldap.tools;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
@@ -42,13 +38,14 @@
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldif.EntryGenerator;
import org.forgerock.util.promise.Promise;
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.ArgumentParser;
@@ -60,6 +57,10 @@
import com.forgerock.opendj.cli.MultiChoiceArgument;
import com.forgerock.opendj.cli.StringArgument;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
/**
 * A load generation tool that can be used to load a Directory Server with Add
 * and Delete requests using one or more LDAP connections.
@@ -68,7 +69,7 @@
    private static final class AddPerformanceRunner extends PerformanceRunner {
        private final class AddStatsHandler extends UpdateStatsResultHandler<Result> {
            private String entryDN;
            private final String entryDN;
            private AddStatsHandler(final long currentTime, String entryDN) {
                super(currentTime);
@@ -146,17 +147,19 @@
            }
            @Override
            public FutureResult<?> performOperation(Connection connection,
                    DataSource[] dataSources, long currentTime) {
            public Promise<?, ErrorResultException> performOperation(Connection connection, DataSource[] dataSources,
                    long currentTime) {
                if (needsDelete(currentTime)) {
                    DeleteRequest dr = Requests.newDeleteRequest(getDNEntryToRemove());
                    return connection.deleteAsync(dr, null, new DeleteStatsHandler(currentTime));
                    ResultHandler<Result> deleteHandler = new DeleteStatsHandler(currentTime);
                    return connection.deleteAsync(dr).onSuccess(deleteHandler).onFailure(deleteHandler);
                } else {
                    return performAddOperation(connection, currentTime);
                }
            }
            private FutureResult<?> performAddOperation(Connection connection, long currentTime) {
            private Promise<Result, ErrorResultException> performAddOperation(Connection connection, long currentTime) {
                try {
                    Entry entry;
                    synchronized (generator) {
@@ -164,12 +167,12 @@
                    }
                    AddRequest ar = Requests.newAddRequest(entry);
                    return connection
                        .addAsync(ar, null, new AddStatsHandler(currentTime, entry.getName().toString()));
                    ResultHandler<Result> addHandler = new AddStatsHandler(currentTime, entry.getName().toString());
                    return connection.addAsync(ar).onSuccess(addHandler).onFailure(addHandler);
                } catch (IOException e) {
                    // faking an error result by notifying the Handler
                    UpdateStatsResultHandler<Result> resHandler = new UpdateStatsResultHandler<Result>(currentTime);
                    resHandler.handleErrorResult(ErrorResultException.newErrorResult(ResultCode.OTHER, e));
                    resHandler.handleError(ErrorResultException.newErrorResult(ResultCode.OTHER, e));
                    return null;
                }
            }
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AuthRate.java
@@ -26,11 +26,6 @@
 */
package com.forgerock.opendj.ldap.tools;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import static com.forgerock.opendj.ldap.tools.Utils.setDefaultPerfToolProperties;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -40,24 +35,11 @@
import java.util.concurrent.atomic.AtomicLong;
import org.forgerock.i18n.LocalizableMessage;
import com.forgerock.opendj.cli.BooleanArgument;
import com.forgerock.opendj.cli.CommonArguments;
import com.forgerock.opendj.cli.ConnectionFactoryProvider;
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.cli.ArgumentParser;
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.IntegerArgument;
import com.forgerock.opendj.cli.MultiChoiceArgument;
import com.forgerock.opendj.cli.StringArgument;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.DereferenceAliasesPolicy;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.CRAMMD5SASLBindRequest;
@@ -70,8 +52,23 @@
import org.forgerock.opendj.ldap.requests.SimpleBindRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.Promise;
import com.forgerock.opendj.util.RecursiveFutureResult;
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.ArgumentParser;
import com.forgerock.opendj.cli.BooleanArgument;
import com.forgerock.opendj.cli.CommonArguments;
import com.forgerock.opendj.cli.ConnectionFactoryProvider;
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.cli.IntegerArgument;
import com.forgerock.opendj.cli.MultiChoiceArgument;
import com.forgerock.opendj.cli.StringArgument;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import static com.forgerock.opendj.ldap.tools.Utils.*;
/**
 * A load generation tool that can be used to load a Directory Server with Bind
@@ -107,8 +104,8 @@
            }
            @Override
            public void handleErrorResult(final ErrorResultException error) {
                super.handleErrorResult(error);
            public void handleError(final ErrorResultException error) {
                super.handleError(error);
                if (error.getResult().getResultCode() == ResultCode.INVALID_CREDENTIALS) {
                    invalidCredRecentCount.getAndIncrement();
@@ -137,7 +134,7 @@
            }
            @Override
            public FutureResult<?> performOperation(final Connection connection,
            public Promise<?, ErrorResultException> performOperation(final Connection connection,
                    final DataSource[] dataSources, final long startTime) {
                if (dataSources != null) {
                    data = DataSource.generateData(dataSources, data);
@@ -147,14 +144,15 @@
                        data = newData;
                    }
                }
                Promise<BindResult, ErrorResultException> returnedPromise;
                if (filter != null && baseDN != null) {
                    if (sr == null) {
                        if (dataSources == null) {
                            sr = Requests.newSearchRequest(baseDN, scope, filter, attributes);
                        } else {
                            sr =
                                    Requests.newSearchRequest(String.format(baseDN, data), scope,
                                            String.format(filter, data), attributes);
                            sr = Requests.newSearchRequest(String.format(baseDN, data), scope,
                                    String.format(filter, data), attributes);
                        }
                        sr.setDereferenceAliasesPolicy(dereferencesAliasesPolicy);
                    } else if (dataSources != null) {
@@ -162,32 +160,30 @@
                        sr.setName(String.format(baseDN, data));
                    }
                    final RecursiveFutureResult<SearchResultEntry, BindResult> future =
                            new RecursiveFutureResult<SearchResultEntry, BindResult>(
                                    new BindUpdateStatsResultHandler(startTime)) {
                    returnedPromise = connection.searchSingleEntryAsync(sr).thenAsync(
                            new AsyncFunction<SearchResultEntry, BindResult, ErrorResultException>() {
                                @Override
                                protected FutureResult<? extends BindResult> chainResult(
                                        final SearchResultEntry innerResult,
                                        final ResultHandler<? super BindResult> resultHandler)
                                public Promise<BindResult, ErrorResultException> apply(SearchResultEntry result)
                                        throws ErrorResultException {
                                    searchWaitRecentTime.getAndAdd(System.nanoTime() - startTime);
                                    if (data == null) {
                                        data = new Object[1];
                                    }
                                    data[data.length - 1] = innerResult.getName().toString();
                                    return performBind(connection, data, resultHandler);
                                    data[data.length - 1] = result.getName().toString();
                                    return performBind(connection, data);
                                }
                            };
                    connection.searchSingleEntryAsync(sr, future);
                    return future;
                            });
                } else {
                    return performBind(connection, data,
                            new BindUpdateStatsResultHandler(startTime));
                    returnedPromise = performBind(connection, data);
                }
                return returnedPromise.onSuccess(new UpdateStatsResultHandler<BindResult>(startTime)).onFailure(
                        new BindUpdateStatsResultHandler(startTime));
            }
            private FutureResult<BindResult> performBind(final Connection connection,
                    final Object[] data, final ResultHandler<? super BindResult> handler) {
            private Promise<BindResult, ErrorResultException> performBind(final Connection connection,
                final Object[] data) {
                final boolean useInvalidPassword;
                // Avoid rng if possible.
@@ -307,7 +303,7 @@
                    }
                }
                return connection.bindAsync(br, null, handler);
                return connection.bindAsync(br);
            }
        }
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java
@@ -26,12 +26,6 @@
 */
package com.forgerock.opendj.ldap.tools;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
import static com.forgerock.opendj.cli.Utils.secondsToTimeString;
import static org.forgerock.util.Utils.closeSilently;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
@@ -89,6 +83,12 @@
import com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl;
import com.forgerock.opendj.util.StaticUtils;
import static org.forgerock.util.Utils.*;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
/**
 * A tool that can be used to issue Search requests to the Directory Server.
 */
@@ -97,6 +97,7 @@
        private int entryCount;
        /** {@inheritDoc} */
        @Override
        public boolean handleEntry(final SearchResultEntry entry) {
            entryCount++;
@@ -179,20 +180,11 @@
        }
        /** {@inheritDoc} */
        @Override
        public boolean handleReference(final SearchResultReference reference) {
            println(LocalizableMessage.raw(reference.toString()));
            return true;
        }
        /** {@inheritDoc} */
        public void handleErrorResult(ErrorResultException error) {
            // Ignore.
        }
        /** {@inheritDoc} */
        public void handleResult(Result result) {
            // Ignore.
        }
    }
    /**
@@ -226,6 +218,7 @@
    }
    /** {@inheritDoc} */
    @Override
    public boolean isInteractive() {
        return false;
    }
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/ModRate.java
@@ -26,19 +26,17 @@
 */
package com.forgerock.opendj.ldap.tools;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.util.promise.Promise;
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.ArgumentParser;
@@ -48,6 +46,10 @@
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.cli.StringArgument;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
/**
 * A load generation tool that can be used to load a Directory Server with
 * Modify requests using one or more LDAP connections.
@@ -64,14 +66,15 @@
            }
            @Override
            public FutureResult<?> performOperation(final Connection connection,
            public Promise<?, ErrorResultException> performOperation(final Connection connection,
                    final DataSource[] dataSources, final long startTime) {
                if (dataSources != null) {
                    data = DataSource.generateData(dataSources, data);
                }
                mr = newModifyRequest(data);
                return connection.modifyAsync(mr, null, new UpdateStatsResultHandler<Result>(
                        startTime));
                ResultHandler<Result> modRes = new UpdateStatsResultHandler<Result>(startTime);
                return connection.modifyAsync(mr).onSuccess(modRes).onFailure(modRes);
            }
            private ModifyRequest newModifyRequest(final Object[] data) {
@@ -139,6 +142,7 @@
    }
    /** {@inheritDoc} */
    @Override
    public boolean isInteractive() {
        return false;
    }
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
@@ -26,9 +26,6 @@
 */
package com.forgerock.opendj.ldap.tools;
import static org.forgerock.util.Utils.closeSilently;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import java.io.IOException;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
@@ -46,21 +43,25 @@
import org.forgerock.opendj.ldap.ConnectionEventListener;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.util.promise.Promise;
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.ArgumentParser;
import com.forgerock.opendj.cli.AuthenticatedConnectionFactory.AuthenticatedConnection;
import com.forgerock.opendj.cli.BooleanArgument;
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.cli.IntegerArgument;
import com.forgerock.opendj.cli.MultiColumnPrinter;
import com.forgerock.opendj.cli.StringArgument;
import com.forgerock.opendj.cli.AuthenticatedConnectionFactory.AuthenticatedConnection;
import com.forgerock.opendj.util.StaticUtils;
import static org.forgerock.util.Utils.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
/**
 * Benchmark application framework.
 */
@@ -338,7 +339,7 @@
    }
    private class TimerThread extends Thread {
        private long timeToWait;
        private final long timeToWait;
        public TimerThread(long timeToWait) {
            this.timeToWait = timeToWait;
@@ -370,7 +371,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            failedRecentCount.getAndIncrement();
            updateStats();
            app.errPrintVerboseMessage(LocalizableMessage.raw(error.getResult().toString()));
@@ -410,22 +411,21 @@
            this.connectionFactory = connectionFactory;
        }
        public abstract FutureResult<?> performOperation(Connection connection,
        public abstract Promise<?, ErrorResultException> performOperation(Connection connection,
                DataSource[] dataSources, long startTime);
        @Override
        public void run() {
            FutureResult<?> future;
            Promise<?, ErrorResultException> promise;
            Connection connection;
            final double targetTimeInMS =
                    1000.0 / (targetThroughput / (double) (numThreads * numConnections));
            final double targetTimeInMS = 1000.0 / (targetThroughput / (double) (numThreads * numConnections));
            double sleepTimeInMS = 0;
            long start;
            while (!stopRequested && !(maxIterations > 0 && count >= maxIterations)) {
                if (this.connection == null) {
                    try {
                        connection = connectionFactory.getConnectionAsync(null).get();
                        connection = connectionFactory.getConnectionAsync().getOrThrow();
                    } catch (final InterruptedException e) {
                        // Ignore and check stop requested
                        continue;
@@ -442,7 +442,7 @@
                    if (!noRebind && connection instanceof AuthenticatedConnection) {
                        final AuthenticatedConnection ac = (AuthenticatedConnection) connection;
                        try {
                            ac.rebindAsync(null).get();
                            ac.rebindAsync().getOrThrow();
                        } catch (final InterruptedException e) {
                            // Ignore and check stop requested
                            continue;
@@ -458,13 +458,13 @@
                }
                start = System.nanoTime();
                future = performOperation(connection, dataSources.get(), start);
                promise = performOperation(connection, dataSources.get(), start);
                operationRecentCount.getAndIncrement();
                count++;
                if (!isAsync) {
                    try {
                        if (future != null) {
                            future.get();
                        if (promise != null) {
                            promise.getOrThrow();
                        }
                    } catch (final InterruptedException e) {
                        // Ignore and check stop requested
@@ -857,7 +857,7 @@
            isWarmingUp = warmUpDuration > 0;
            for (int i = 0; i < numConnections; i++) {
                if (keepConnectionsOpen.isPresent() || noRebindArgument.isPresent()) {
                    connection = connectionFactory.getConnectionAsync(null).get();
                    connection = connectionFactory.getConnectionAsync().getOrThrow();
                    connection.addConnectionEventListener(this);
                    connections.add(connection);
                }
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/SearchRate.java
@@ -26,10 +26,6 @@
 */
package com.forgerock.opendj.ldap.tools;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -39,7 +35,7 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.DereferenceAliasesPolicy;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
@@ -48,6 +44,7 @@
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.Promise;
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.ArgumentParser;
@@ -58,6 +55,10 @@
import com.forgerock.opendj.cli.MultiChoiceArgument;
import com.forgerock.opendj.cli.StringArgument;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
/**
 * A load generation tool that can be used to load a Directory Server with
 * Search requests using one or more LDAP connections.
@@ -117,7 +118,7 @@
            }
            @Override
            public FutureResult<?> performOperation(final Connection connection,
            public Promise<?, ErrorResultException> performOperation(final Connection connection,
                    final DataSource[] dataSources, final long startTime) {
                if (sr == null) {
                    if (dataSources == null) {
@@ -134,7 +135,10 @@
                    sr.setFilter(String.format(filter, data));
                    sr.setName(String.format(baseDN, data));
                }
                return connection.searchAsync(sr, null, new SearchStatsHandler(startTime));
                final SearchStatsHandler handler = new SearchStatsHandler(startTime);
                return connection.searchAsync(sr, handler).onSuccess(handler).onFailure(handler);
            }
        }
opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java
@@ -11,22 +11,11 @@
 * 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-2014 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap.servlet;
import static org.forgerock.json.resource.SecurityContext.AUTHZID_DN;
import static org.forgerock.json.resource.SecurityContext.AUTHZID_ID;
import static org.forgerock.json.resource.servlet.SecurityContextFactory.ATTRIBUTE_AUTHCID;
import static org.forgerock.json.resource.servlet.SecurityContextFactory.ATTRIBUTE_AUTHZID;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.requests.Requests.newPlainSASLBindRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPContextFactory.ATTRIBUTE_AUTHN_CONNECTION;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@@ -49,8 +38,8 @@
import org.forgerock.json.fluent.JsonValue;
import org.forgerock.json.fluent.JsonValueException;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.servlet.ServletSynchronizer;
import org.forgerock.json.resource.servlet.ServletApiVersionAdapter;
import org.forgerock.json.resource.servlet.ServletSynchronizer;
import org.forgerock.opendj.ldap.AuthenticationException;
import org.forgerock.opendj.ldap.AuthorizationException;
import org.forgerock.opendj.ldap.ByteString;
@@ -62,7 +51,6 @@
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -70,6 +58,17 @@
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.rest2ldap.Rest2LDAP;
import org.forgerock.util.promise.AsyncFunction;
import org.forgerock.util.promise.FailureHandler;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.SuccessHandler;
import static org.forgerock.json.resource.SecurityContext.*;
import static org.forgerock.json.resource.servlet.SecurityContextFactory.*;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
import static org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPContextFactory.*;
/**
 * An LDAP based authentication Servlet filter.
@@ -115,8 +114,8 @@
    }
    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
            final FilterChain chain) throws IOException, ServletException {
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
            throws IOException, ServletException {
        // Skip this filter if authentication has not been configured.
        if (!isEnabled) {
            chain.doFilter(request, response);
@@ -149,14 +148,11 @@
        });
        try {
            final String headerUsername =
                    supportAltAuthentication ? req.getHeader(altAuthenticationUsernameHeader)
                            : null;
            final String headerPassword =
                    supportAltAuthentication ? req.getHeader(altAuthenticationPasswordHeader)
                            : null;
            final String headerAuthorization =
                    supportHTTPBasicAuthentication ? req.getHeader("Authorization") : null;
            final String headerUsername = supportAltAuthentication ? req.getHeader(altAuthenticationUsernameHeader)
                    : null;
            final String headerPassword = supportAltAuthentication ? req.getHeader(altAuthenticationPasswordHeader)
                    : null;
            final String headerAuthorization = supportHTTPBasicAuthentication ? req.getHeader("Authorization") : null;
            final String username;
            final char[] password;
@@ -194,17 +190,15 @@
                authzid = new LinkedHashMap<String, Object>(2);
                authzid.put(AUTHZID_DN, username);
                authzid.put(AUTHZID_ID, username);
                doBind(req, res, newSimpleBindRequest(username, password), chain, savedConnection,
                        sync, username, authzid);
                doBind(req, res, newSimpleBindRequest(username, password), chain, savedConnection, sync, username,
                        authzid);
                break;
            }
            case SASL_PLAIN: {
                final Map<String, Object> authzid;
                final String bindId;
                if (saslAuthzIdTemplate.startsWith("dn:")) {
                    final String bindDN =
                            DN.format(saslAuthzIdTemplate.substring(3), schema, username)
                                    .toString();
                    final String bindDN = DN.format(saslAuthzIdTemplate.substring(3), schema, username).toString();
                    bindId = "dn:" + bindDN;
                    authzid = new LinkedHashMap<String, Object>(2);
                    authzid.put(AUTHZID_DN, bindDN);
@@ -213,8 +207,8 @@
                    bindId = String.format(saslAuthzIdTemplate, username);
                    authzid = Collections.singletonMap(AUTHZID_ID, (Object) username);
                }
                doBind(req, res, newPlainSASLBindRequest(bindId, password), chain, savedConnection,
                        sync, username, authzid);
                doBind(req, res, newPlainSASLBindRequest(bindId, password), chain, savedConnection, sync, username,
                        authzid);
                break;
            }
            default: // SEARCH_SIMPLE
@@ -223,61 +217,52 @@
                 * First do a search to find the user's entry and then perform a
                 * bind request using the user's DN.
                 */
                final org.forgerock.opendj.ldap.Filter filter =
                        org.forgerock.opendj.ldap.Filter.format(searchFilterTemplate, username);
                final SearchRequest searchRequest =
                        newSearchRequest(searchBaseDN, searchScope, filter, "1.1");
                searchLDAPConnectionFactory.getConnectionAsync(new ResultHandler<Connection>() {
                    @Override
                    public void handleErrorResult(final ErrorResultException error) {
                        sync.signalAndComplete(asResourceException(error));
                    }
                    @Override
                    public void handleResult(final Connection connection) {
                        // Do the search.
                        connection.searchSingleEntryAsync(searchRequest,
                                new ResultHandler<SearchResultEntry>() {
                                    @Override
                                    public void handleErrorResult(final ErrorResultException error) {
                                        connection.close();
                                        /*
                                         * The search error should not be passed
                                         * as-is back to the user.
                                         */
                                        final ErrorResultException normalizedError;
                                        if (error instanceof EntryNotFoundException
                                                || error instanceof MultipleEntriesFoundException) {
                                            normalizedError =
                                                    newErrorResult(ResultCode.INVALID_CREDENTIALS,
                                                            error);
                                        } else if (error instanceof AuthenticationException
                                                || error instanceof AuthorizationException) {
                                            normalizedError =
                                                    newErrorResult(
                                                            ResultCode.CLIENT_SIDE_LOCAL_ERROR,
                                                            error);
                                        } else {
                                            normalizedError = error;
                                        }
                                        sync.signalAndComplete(asResourceException(normalizedError));
                final org.forgerock.opendj.ldap.Filter filter = org.forgerock.opendj.ldap.Filter.format(
                        searchFilterTemplate, username);
                final SearchRequest searchRequest = newSearchRequest(searchBaseDN, searchScope, filter, "1.1");
                searchLDAPConnectionFactory.getConnectionAsync()
                        .thenAsync(new AsyncFunction<Connection, SearchResultEntry, ErrorResultException>() {
                            @Override
                            public Promise<SearchResultEntry, ErrorResultException> apply(Connection connection)
                                    throws ErrorResultException {
                                savedConnection.set(connection);
                                // Do the search.
                                return connection.searchSingleEntryAsync(searchRequest);
                            }
                        }).onSuccess(new SuccessHandler<SearchResultEntry>() {
                            @Override
                            public void handleResult(final SearchResultEntry result) {
                                savedConnection.get().close();
                                final String bindDN = result.getName().toString();
                                final Map<String, Object> authzid = new LinkedHashMap<String, Object>(2);
                                authzid.put(AUTHZID_DN, bindDN);
                                authzid.put(AUTHZID_ID, username);
                                doBind(req, res, newSimpleBindRequest(bindDN, password), chain, savedConnection, sync,
                                        username, authzid);
                            }
                        }).onFailure(new FailureHandler<ErrorResultException>() {
                            @Override
                            public void handleError(final ErrorResultException error) {
                                ErrorResultException normalizedError = error;
                                if (savedConnection.get() != null) {
                                    savedConnection.get().close();
                                    /*
                                     * The search error should not be passed
                                     * as-is back to the user.
                                     */
                                    if (error instanceof EntryNotFoundException
                                            || error instanceof MultipleEntriesFoundException) {
                                        normalizedError = newErrorResult(ResultCode.INVALID_CREDENTIALS, error);
                                    } else if (error instanceof AuthenticationException
                                            || error instanceof AuthorizationException) {
                                        normalizedError = newErrorResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR, error);
                                    } else {
                                        normalizedError = error;
                                    }
                                    @Override
                                    public void handleResult(final SearchResultEntry result) {
                                        connection.close();
                                        final String bindDN = result.getName().toString();
                                        final Map<String, Object> authzid =
                                                new LinkedHashMap<String, Object>(2);
                                        authzid.put(AUTHZID_DN, bindDN);
                                        authzid.put(AUTHZID_ID, username);
                                        doBind(req, res, newSimpleBindRequest(bindDN, password),
                                                chain, savedConnection, sync, username, authzid);
                                    }
                                });
                    }
                });
                                }
                                sync.signalAndComplete(asResourceException(normalizedError));
                            }
                        });
                break;
            }
            }
@@ -406,25 +391,17 @@
     * cached connection and authorization credentials on completion.
     */
    private void doBind(final HttpServletRequest request, final ServletResponse response,
            final BindRequest bindRequest, final FilterChain chain,
            final AtomicReference<Connection> savedConnection, final ServletSynchronizer sync,
            final String authcid, final Map<String, Object> authzid) {
        bindLDAPConnectionFactory.getConnectionAsync(new ResultHandler<Connection>() {
            @Override
            public void handleErrorResult(final ErrorResultException error) {
                sync.signalAndComplete(asResourceException(error));
            }
            @Override
            public void handleResult(final Connection connection) {
                savedConnection.set(connection);
                connection.bindAsync(bindRequest, null, new ResultHandler<BindResult>() {
            final BindRequest bindRequest, final FilterChain chain, final AtomicReference<Connection> savedConnection,
            final ServletSynchronizer sync, final String authcid, final Map<String, Object> authzid) {
        bindLDAPConnectionFactory.getConnectionAsync()
                .thenAsync(new AsyncFunction<Connection, BindResult, ErrorResultException>() {
                    @Override
                    public void handleErrorResult(final ErrorResultException error) {
                        sync.signalAndComplete(asResourceException(error));
                    public Promise<BindResult, ErrorResultException> apply(Connection connection)
                            throws ErrorResultException {
                        savedConnection.set(connection);
                        return connection.bindAsync(bindRequest);
                    }
                }).onSuccess(new SuccessHandler<BindResult>() {
                    @Override
                    public void handleResult(final BindResult result) {
                        /*
@@ -433,8 +410,8 @@
                         * filter will close it.
                         */
                        if (reuseAuthenticatedConnection) {
                            request.setAttribute(ATTRIBUTE_AUTHN_CONNECTION, Connections
                                    .uncloseable(connection));
                            request.setAttribute(ATTRIBUTE_AUTHN_CONNECTION,
                                    Connections.uncloseable(savedConnection.get()));
                        }
                        // Pass through the authentication ID and authorization principals.
@@ -462,9 +439,12 @@
                            }
                        }
                    }
                }).onFailure(new FailureHandler<ErrorResultException>() {
                    @Override
                    public void handleError(final ErrorResultException error) {
                        sync.signalAndComplete(asResourceException(error));
                    }
                });
            }
        });
    }
    private AuthenticationMethod parseAuthenticationMethod(final JsonValue configuration) {
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
@@ -11,14 +11,10 @@
 * 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-2014 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import java.io.Closeable;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -58,6 +54,11 @@
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.FailureHandler;
import org.forgerock.util.promise.SuccessHandler;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
import static org.forgerock.opendj.rest2ldap.Utils.*;
/**
 * Common context information passed to containers and mappers. A new context is
@@ -68,7 +69,7 @@
    /*
     * A cached read request - see cachedReads for more information.
     */
    private static final class CachedRead implements SearchResultHandler {
    private static final class CachedRead implements SearchResultHandler, ResultHandler<Result> {
        private SearchResultEntry cachedEntry;
        private final String cachedFilterString;
        private FutureResult<Result> cachedFuture; // Guarded by latch.
@@ -91,7 +92,7 @@
        }
        @Override
        public void handleErrorResult(final ErrorResultException error) {
        public void handleError(final ErrorResultException error) {
            handleResult(error.getResult());
        }
@@ -171,14 +172,9 @@
            }
        }
        private void invokeResultHandler(final SearchResultHandler resultHandler) {
        private void invokeResultHandler(final SearchResultHandler searchResultHandler) {
            if (cachedEntry != null) {
                resultHandler.handleEntry(cachedEntry);
            }
            if (cachedResult.isSuccess()) {
                resultHandler.handleResult(cachedResult);
            } else {
                resultHandler.handleErrorResult(newErrorResult(cachedResult));
                searchResultHandler.handleEntry(cachedEntry);
            }
        }
@@ -214,9 +210,7 @@
         */
        if (config.getAuthorizationPolicy() != AuthorizationPolicy.NONE
                && context.containsContext(AuthenticatedConnectionContext.class)) {
            final Connection connection =
                    context.asContext(AuthenticatedConnectionContext.class).getConnection();
            this.connection = wrap(connection);
            this.connection = wrap(context.asContext(AuthenticatedConnectionContext.class).getConnection());
        } else {
            this.connection = null; // We'll allocate the connection.
        }
@@ -268,11 +262,9 @@
        if (connection == null && config.getAuthorizationPolicy() == AuthorizationPolicy.PROXY) {
            if (context.containsContext(SecurityContext.class)) {
                try {
                    final SecurityContext securityContext =
                            context.asContext(SecurityContext.class);
                    final String authzId =
                            config.getProxiedAuthorizationTemplate().formatAsAuthzId(
                                    securityContext.getAuthorizationId(), config.schema());
                    final SecurityContext securityContext = context.asContext(SecurityContext.class);
                    final String authzId = config.getProxiedAuthorizationTemplate().formatAsAuthzId(
                            securityContext.getAuthorizationId(), config.schema());
                    proxiedAuthzControl = ProxiedAuthV2RequestControl.newControl(authzId);
                } catch (final ResourceException e) {
                    handler.handleError(e);
@@ -280,8 +272,7 @@
                }
            } else {
                handler.handleError(new InternalServerErrorException(
                        i18n("The request could not be authorized because it did "
                                + "not contain a security context")));
                        i18n("The request could not be authorized because it did not contain a security context")));
                return;
            }
        }
@@ -295,22 +286,21 @@
            // Invoke the handler immediately since a connection is available.
            runnable.run();
        } else if (config.connectionFactory() != null) {
            config.connectionFactory().getConnectionAsync(new ResultHandler<Connection>() {
                @Override
                public final void handleErrorResult(final ErrorResultException error) {
                    handler.handleError(asResourceException(error));
                }
            config.connectionFactory().getConnectionAsync().onSuccess(new SuccessHandler<Connection>() {
                @Override
                public final void handleResult(final Connection result) {
                    connection = wrap(result);
                    runnable.run();
                }
            }).onFailure(new FailureHandler<ErrorResultException>() {
                @Override
                public final void handleError(final ErrorResultException error) {
                    handler.handleError(asResourceException(error));
                }
            });
        } else {
            handler.handleError(new InternalServerErrorException(
                    i18n("The request could not be processed because there was no LDAP "
                            + "connection available for use")));
                    i18n("The request could not be processed because there was no LDAP connection available for use")));
        }
    }
@@ -331,10 +321,8 @@
            @Override
            public FutureResult<Result> addAsync(final AddRequest request,
                    final IntermediateResponseHandler intermediateResponseHandler,
                    final ResultHandler<? super Result> resultHandler) {
                return connection.addAsync(withControls(request), intermediateResponseHandler,
                        resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
                return connection.addAsync(withControls(request), intermediateResponseHandler);
            }
            @Override
@@ -344,14 +332,13 @@
            @Override
            public FutureResult<BindResult> bindAsync(final BindRequest request,
                    final IntermediateResponseHandler intermediateResponseHandler,
                    final ResultHandler<? super BindResult> resultHandler) {
                final IntermediateResponseHandler intermediateResponseHandler) {
                /*
                 * Simple brute force implementation in case the bind operation
                 * modifies an entry: clear the cachedReads.
                 */
                evictAll();
                return connection.bindAsync(request, intermediateResponseHandler, resultHandler);
                return connection.bindAsync(request, intermediateResponseHandler);
            }
            @Override
@@ -366,33 +353,26 @@
            @Override
            public FutureResult<CompareResult> compareAsync(final CompareRequest request,
                    final IntermediateResponseHandler intermediateResponseHandler,
                    final ResultHandler<? super CompareResult> resultHandler) {
                return connection.compareAsync(withControls(request), intermediateResponseHandler,
                        resultHandler);
                final IntermediateResponseHandler intermediateResponseHandler) {
                return connection.compareAsync(withControls(request), intermediateResponseHandler);
            }
            @Override
            public FutureResult<Result> deleteAsync(final DeleteRequest request,
                    final IntermediateResponseHandler intermediateResponseHandler,
                    final ResultHandler<? super Result> resultHandler) {
                final IntermediateResponseHandler intermediateResponseHandler) {
                evict(request.getName());
                return connection.deleteAsync(withControls(request), intermediateResponseHandler,
                        resultHandler);
                return connection.deleteAsync(withControls(request), intermediateResponseHandler);
            }
            @Override
            public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
                    final ExtendedRequest<R> request,
                    final IntermediateResponseHandler intermediateResponseHandler,
                    final ResultHandler<? super R> resultHandler) {
            public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(final ExtendedRequest<R> request,
                final IntermediateResponseHandler intermediateResponseHandler) {
                /*
                 * Simple brute force implementation in case the extended
                 * operation modifies an entry: clear the cachedReads.
                 */
                evictAll();
                return connection.extendedRequestAsync(withControls(request),
                        intermediateResponseHandler, resultHandler);
                return connection.extendedRequestAsync(withControls(request), intermediateResponseHandler);
            }
            @Override
@@ -407,21 +387,17 @@
            @Override
            public FutureResult<Result> modifyAsync(final ModifyRequest request,
                    final IntermediateResponseHandler intermediateResponseHandler,
                    final ResultHandler<? super Result> resultHandler) {
                final IntermediateResponseHandler intermediateResponseHandler) {
                evict(request.getName());
                return connection.modifyAsync(withControls(request), intermediateResponseHandler,
                        resultHandler);
                return connection.modifyAsync(withControls(request), intermediateResponseHandler);
            }
            @Override
            public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
                    final IntermediateResponseHandler intermediateResponseHandler,
                    final ResultHandler<? super Result> resultHandler) {
                final IntermediateResponseHandler intermediateResponseHandler) {
                // Simple brute force implementation: clear the cachedReads.
                evictAll();
                return connection.modifyDNAsync(withControls(request), intermediateResponseHandler,
                        resultHandler);
                return connection.modifyDNAsync(withControls(request), intermediateResponseHandler);
            }
            @Override
@@ -434,17 +410,14 @@
             */
            @Override
            public FutureResult<Result> searchAsync(final SearchRequest request,
                    final IntermediateResponseHandler intermediateResponseHandler,
                    final SearchResultHandler resultHandler) {
                final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
                /*
                 * Don't attempt caching if this search is not a read (base
                 * object), or if the search request passed in an intermediate
                 * response handler.
                 */
                if (!request.getScope().equals(SearchScope.BASE_OBJECT)
                        || intermediateResponseHandler != null) {
                    return connection.searchAsync(withControls(request),
                            intermediateResponseHandler, resultHandler);
                if (!request.getScope().equals(SearchScope.BASE_OBJECT) || intermediateResponseHandler != null) {
                    return connection.searchAsync(withControls(request), intermediateResponseHandler, entryHandler);
                }
                // This is a read request and a candidate for caching.
@@ -454,17 +427,17 @@
                }
                if (cachedRead != null && cachedRead.isMatchingRead(request)) {
                    // The cached read matches this read request.
                    cachedRead.addResultHandler(resultHandler);
                    cachedRead.addResultHandler(entryHandler);
                    return cachedRead.getFutureResult();
                } else {
                    // Cache the read, possibly evicting a non-matching cached read.
                    final CachedRead pendingCachedRead = new CachedRead(request, resultHandler);
                    final CachedRead pendingCachedRead = new CachedRead(request, entryHandler);
                    synchronized (cachedReads) {
                        cachedReads.put(request.getName(), pendingCachedRead);
                    }
                    final FutureResult<Result> future =
                            connection.searchAsync(withControls(request),
                                    intermediateResponseHandler, pendingCachedRead);
                    final FutureResult<Result> future = (FutureResult<Result>) connection
                            .searchAsync(withControls(request), intermediateResponseHandler, pendingCachedRead)
                            .onSuccess(pendingCachedRead).onFailure(pendingCachedRead);
                    pendingCachedRead.setFuture(future);
                    return future;
                }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -11,24 +11,11 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2012-2013 ForgeRock AS.
 * Copyright 2012-2014 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
import static java.util.Arrays.asList;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
import static org.forgerock.opendj.ldap.requests.Requests.newAddRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newDeleteRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newModifyRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import static org.forgerock.opendj.rest2ldap.Utils.transform;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@@ -86,16 +73,48 @@
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.FailureHandler;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.forgerock.util.promise.Promises;
import org.forgerock.util.promise.SuccessHandler;
import static java.util.Arrays.*;
import static org.forgerock.opendj.ldap.Filter.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.*;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
import static org.forgerock.opendj.rest2ldap.Utils.*;
/**
 * A {@code CollectionResourceProvider} implementation which maps a JSON
 * resource collection to LDAP entries beneath a base DN.
 */
final class LDAPCollectionResourceProvider implements CollectionResourceProvider {
    // Dummy exception used for signalling search success.
    private static class ResultHandlerFromPromise<T> implements ResultHandler<T> {
        private final PromiseImpl<T, ResourceException> promise;
        ResultHandlerFromPromise() {
            promise = PromiseImpl.create();
        }
        @Override
        public void handleError(ResourceException error) {
            promise.handleError(error);
        }
        @Override
        public void handleResult(T result) {
            promise.handleResult(result);
        }
    }
    /** Dummy exception used for signalling search success. */
    private static final ResourceException SUCCESS = new UncategorizedException(0, null, null);
    // Empty decode options required for decoding response controls.
    /** Empty decode options required for decoding response controls. */
    private static final DecodeOptions DECODE_OPTIONS = new DecodeOptions();
    private final List<Attribute> additionalLDAPAttributes;
@@ -140,39 +159,37 @@
            public void run() {
                // Calculate entry content.
                attributeMapper.create(c, new JsonPointer(), request.getContent(),
                        new ResultHandler<List<Attribute>>() {
                            @Override
                            public void handleError(final ResourceException error) {
                                h.handleError(error);
                            }
                    new ResultHandler<List<Attribute>>() {
                        @Override
                        public void handleError(final ResourceException error) {
                            h.handleError(error);
                        }
                            @Override
                            public void handleResult(final List<Attribute> result) {
                                // Perform add operation.
                                final AddRequest addRequest = newAddRequest(DN.rootDN());
                                for (final Attribute attribute : additionalLDAPAttributes) {
                                    addRequest.addAttribute(attribute);
                                }
                                for (final Attribute attribute : result) {
                                    addRequest.addAttribute(attribute);
                                }
                                try {
                                    nameStrategy.setResourceId(c, getBaseDN(c), request
                                            .getNewResourceId(), addRequest);
                                } catch (final ResourceException e) {
                                    h.handleError(e);
                                    return;
                                }
                                if (config.readOnUpdatePolicy() == CONTROLS) {
                                    final String[] attributes =
                                            getLDAPAttributes(c, request.getFields());
                                    addRequest.addControl(PostReadRequestControl.newControl(false,
                                            attributes));
                                }
                                c.getConnection().applyChangeAsync(addRequest, null,
                                        postUpdateHandler(c, h));
                        @Override
                        public void handleResult(final List<Attribute> result) {
                            // Perform add operation.
                            final AddRequest addRequest = newAddRequest(DN.rootDN());
                            for (final Attribute attribute : additionalLDAPAttributes) {
                                addRequest.addAttribute(attribute);
                            }
                        });
                            for (final Attribute attribute : result) {
                                addRequest.addAttribute(attribute);
                            }
                            try {
                                nameStrategy.setResourceId(c, getBaseDN(c), request.getNewResourceId(), addRequest);
                            } catch (final ResourceException e) {
                                h.handleError(e);
                                return;
                            }
                            if (config.readOnUpdatePolicy() == CONTROLS) {
                                final String[] attributes = getLDAPAttributes(c, request.getFields());
                                addRequest.addControl(PostReadRequestControl.newControl(false, attributes));
                            }
                            c.getConnection().applyChangeAsync(addRequest)
                                        .onSuccess(postUpdateSuccessHandler(c, h))
                                        .onFailure(postUpdateFailureHandler(h));
                        }
                    });
            }
        });
    }
@@ -196,15 +213,14 @@
                    final ChangeRecord deleteRequest = newDeleteRequest(dn);
                    if (config.readOnUpdatePolicy() == CONTROLS) {
                        final String[] attributes = getLDAPAttributes(c, request.getFields());
                        deleteRequest.addControl(PreReadRequestControl
                                .newControl(false, attributes));
                        deleteRequest.addControl(PreReadRequestControl.newControl(false, attributes));
                    }
                    if (config.useSubtreeDelete()) {
                        deleteRequest.addControl(SubtreeDeleteRequestControl.newControl(true));
                    }
                    addAssertionControl(deleteRequest, request.getRevision());
                    c.getConnection()
                            .applyChangeAsync(deleteRequest, null, postUpdateHandler(c, h));
                    c.getConnection().applyChangeAsync(deleteRequest).onSuccess(postUpdateSuccessHandler(c, h))
                            .onFailure(postUpdateFailureHandler(h));
                } catch (final Exception e) {
                    h.handleError(asResourceException(e));
                }
@@ -213,31 +229,29 @@
    }
    @Override
    public void patchInstance(final ServerContext context, final String resourceId,
            final PatchRequest request, final ResultHandler<Resource> handler) {
    public void patchInstance(final ServerContext context, final String resourceId, final PatchRequest request,
            final ResultHandler<Resource> handler) {
        final Context c = wrap(context);
        final ResultHandler<Resource> h = wrap(c, handler);
        if (request.getPatchOperations().isEmpty()) {
            /*
             * This patch is a no-op so just read the entry and check its
             * version.
             * This patch is a no-op so just read the entry and check its version.
             */
            c.run(h, new Runnable() {
                @Override
                public void run() {
                    final String[] attributes = getLDAPAttributes(c, request.getFields());
                    final SearchRequest searchRequest =
                            nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId)
                                    .addAttribute(attributes);
                    c.getConnection().searchSingleEntryAsync(searchRequest,
                            postEmptyPatchHandler(c, request, h));
                    final SearchRequest searchRequest = nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId)
                            .addAttribute(attributes);
                    c.getConnection().searchSingleEntryAsync(searchRequest)
                            .onSuccess(postEmptyPatchSuccessHandler(c, request, h))
                            .onFailure(postEmptyPatchFailureHandler(h));
                }
            });
        } else {
            /*
             * Get the connection, search if needed, then determine
             * modifications, then perform modify.
             * Get the connection, search if needed, then determine modifications, then perform modify.
             */
            c.run(h, doUpdate(c, resourceId, request.getRevision(), new ResultHandler<DN>() {
                @Override
@@ -247,71 +261,62 @@
                @Override
                public void handleResult(final DN dn) {
                    //  Convert the patch operations to LDAP modifications.
                    final ResultHandler<List<Modification>> handler =
                            accumulate(request.getPatchOperations().size(),
                                    new ResultHandler<List<List<Modification>>>() {
                                        @Override
                                        public void handleError(final ResourceException error) {
                                            h.handleError(error);
                                        }
                                        @Override
                                        public void handleResult(
                                                final List<List<Modification>> result) {
                                            //  The patch operations have been converted successfully.
                                            try {
                                                final ModifyRequest modifyRequest =
                                                        newModifyRequest(dn);
                                                // Add the modifications.
                                                for (final List<Modification> modifications : result) {
                                                    if (modifications != null) {
                                                        modifyRequest.getModifications().addAll(
                                                                modifications);
                                                    }
                                                }
                                                final List<String> attributes =
                                                        asList(getLDAPAttributes(c, request
                                                                .getFields()));
                                                if (modifyRequest.getModifications().isEmpty()) {
                                                    /*
                                                     * This patch is a no-op so
                                                     * just read the entry and
                                                     * check its version.
                                                     */
                                                    c.getConnection().readEntryAsync(dn,
                                                            attributes,
                                                            postEmptyPatchHandler(c, request, h));
                                                } else {
                                                    // Add controls and perform the modify request.
                                                    if (config.readOnUpdatePolicy() == CONTROLS) {
                                                        modifyRequest
                                                                .addControl(PostReadRequestControl
                                                                        .newControl(false,
                                                                                attributes));
                                                    }
                                                    if (config.usePermissiveModify()) {
                                                        modifyRequest
                                                                .addControl(PermissiveModifyRequestControl
                                                                        .newControl(true));
                                                    }
                                                    addAssertionControl(modifyRequest, request
                                                            .getRevision());
                                                    c.getConnection().applyChangeAsync(
                                                            modifyRequest, null,
                                                            postUpdateHandler(c, h));
                                                }
                                            } catch (final Exception e) {
                                                h.handleError(asResourceException(e));
                                            }
                                        }
                                    });
                    // Convert the patch operations to LDAP modifications.
                    List<Promise<List<Modification>, ResourceException>> promises =
                            new ArrayList<Promise<List<Modification>, ResourceException>>(
                                    request.getPatchOperations().size());
                    for (final PatchOperation operation : request.getPatchOperations()) {
                        final ResultHandlerFromPromise<List<Modification>> handler =
                                new ResultHandlerFromPromise<List<Modification>>();
                        attributeMapper.patch(c, new JsonPointer(), operation, handler);
                        promises.add(handler.promise);
                    }
                    Promises.when(promises).onSuccess(new SuccessHandler<List<List<Modification>>>() {
                        @Override
                        public void handleResult(final List<List<Modification>> result) {
                            // The patch operations have been converted successfully.
                            try {
                                final ModifyRequest modifyRequest = newModifyRequest(dn);
                                // Add the modifications.
                                for (final List<Modification> modifications : result) {
                                    if (modifications != null) {
                                        modifyRequest.getModifications().addAll(modifications);
                                    }
                                }
                                final List<String> attributes = asList(getLDAPAttributes(c, request.getFields()));
                                if (modifyRequest.getModifications().isEmpty()) {
                                    /*
                                     * This patch is a no-op so just read the entry and check its version.
                                     */
                                    c.getConnection().readEntryAsync(dn, attributes)
                                            .onSuccess(postEmptyPatchSuccessHandler(c, request, h))
                                            .onFailure(postEmptyPatchFailureHandler(h));
                                } else {
                                    // Add controls and perform the modify request.
                                    if (config.readOnUpdatePolicy() == CONTROLS) {
                                        modifyRequest.addControl(PostReadRequestControl.newControl(false, attributes));
                                    }
                                    if (config.usePermissiveModify()) {
                                        modifyRequest.addControl(PermissiveModifyRequestControl.newControl(true));
                                    }
                                    addAssertionControl(modifyRequest, request.getRevision());
                                    c.getConnection().applyChangeAsync(modifyRequest)
                                            .onSuccess(postUpdateSuccessHandler(c, h))
                                            .onFailure(postUpdateFailureHandler(h));
                                }
                            } catch (final Exception e) {
                                h.handleError(asResourceException(e));
                            }
                        }
                    }).onFailure(new FailureHandler<ResourceException>() {
                        @Override
                        public void handleError(ResourceException error) {
                            h.handleError(asResourceException(error));
                        }
                    });
                }
            }));
        }
@@ -324,14 +329,25 @@
        final QueryResultHandler h = wrap(c, handler);
        /*
         * Get the connection, then calculate the search filter, then perform
         * the search.
         * Get the connection, then calculate the search filter, then perform the search.
         */
        c.run(h, new Runnable() {
            @Override
            public void run() {
                // Calculate the filter (this may require the connection).
                getLDAPFilter(c, request.getQueryFilter(), new ResultHandler<Filter>() {
                    /**
                     * The following fields are guarded by sequenceLock. In
                     * addition, the sequenceLock ensures that we send one JSON
                     * resource at a time back to the client.
                     */
                    private final Object sequenceLock = new Object();
                    private String cookie;
                    private ResourceException pendingResult;
                    private int pendingResourceCount;
                    private boolean resultSent;
                    private int totalResourceCount;
                    @Override
                    public void handleError(final ResourceException error) {
                        h.handleError(error);
@@ -340,23 +356,21 @@
                    @Override
                    public void handleResult(final Filter ldapFilter) {
                        /*
                         * Avoid performing a search if the filter could not be
                         * mapped or if it will never match.
                         * Avoid performing a search if the filter could not be mapped or if it will never match.
                         */
                        if (ldapFilter == null || ldapFilter == alwaysFalse()) {
                            h.handleResult(new QueryResult());
                        } else {
                            // Perform the search.
                            final String[] attributes = getLDAPAttributes(c, request.getFields());
                            final Filter searchFilter =
                                    ldapFilter == Filter.alwaysTrue() ? Filter.objectClassPresent() : ldapFilter;
                            final SearchRequest searchRequest =
                                    newSearchRequest(getBaseDN(c), SearchScope.SINGLE_LEVEL,
                                            ldapFilter == Filter.alwaysTrue() ? Filter
                                                    .objectClassPresent() : ldapFilter, attributes);
                                    newSearchRequest(getBaseDN(c), SearchScope.SINGLE_LEVEL, searchFilter, attributes);
                            /*
                             * Add the page results control. We can support the
                             * page offset by reading the next offset pages, or
                             * offset x page size resources.
                             * Add the page results control. We can support the page offset by
                             * reading the next offset pages, or offset x page size resources.
                             */
                            final int pageResultStartIndex;
                            final int pageSize = request.getPageSize();
@@ -374,35 +388,18 @@
                                                .valueOfBase64(request.getPagedResultsCookie())
                                                : ByteString.empty();
                                final SimplePagedResultsControl control =
                                        SimplePagedResultsControl.newControl(true,
                                                pageResultEndIndex, cookie);
                                        SimplePagedResultsControl.newControl(true, pageResultEndIndex, cookie);
                                searchRequest.addControl(control);
                            } else {
                                pageResultStartIndex = 0;
                            }
                            c.getConnection().searchAsync(searchRequest, null, new SearchResultHandler() {
                                /*
                                 * The following fields are guarded by
                                 * sequenceLock. In addition, the sequenceLock
                                 * ensures that we send one JSON resource at a
                                 * time back to the client.
                                 */
                                private final Object sequenceLock = new Object();
                                private int pendingResourceCount = 0;
                                private ResourceException pendingResult = null;
                                private boolean resultSent = false;
                                private int totalResourceCount = 0;
                                private String cookie = null;
                            c.getConnection().searchAsync(searchRequest, new SearchResultHandler() {
                                @Override
                                public boolean handleEntry(final SearchResultEntry entry) {
                                    /*
                                     * Search result entries will be returned
                                     * before the search result/error so the
                                     * only reason pendingResult will be
                                     * non-null is if a mapping error has
                                     * occurred.
                                     * Search result entries will be returned before the search result/error so the
                                     * only reason pendingResult will be non-null is if a mapping error has occurred.
                                     */
                                    synchronized (sequenceLock) {
                                        if (pendingResult != null) {
@@ -416,75 +413,59 @@
                                    }
                                    /*
                                     * FIXME: secondary asynchronous searches
                                     * will complete in a non-deterministic
                                     * order and may cause the JSON resources to
                                     * be returned in a different order to the
                                     * order in which the primary LDAP search
                                     * results were received. This is benign at
                                     * the moment, but will need resolving when
                                     * we implement server side sorting. A
                                     * possible fix will be to use a queue of
                                     * pending resources (futures?). However,
                                     * the queue cannot be unbounded in case it
                                     * grows very large, but it cannot be
                                     * bounded either since that could cause a
                                     * deadlock between rest2ldap and the LDAP
                                     * server (imagine the case where the server
                                     * has a single worker thread which is
                                     * FIXME: secondary asynchronous searches will complete in a non-deterministic
                                     * order and may cause the JSON resources to be returned in a different order to the
                                     * order in which the primary LDAP search results were received. This is benign at
                                     * the moment, but will need resolving when we implement server side sorting. A
                                     * possible fix will be to use a queue of pending resources (futures?). However,
                                     * the queue cannot be unbounded in case it grows very large, but it cannot be
                                     * bounded either since that could cause a deadlock between rest2ldap and the LDAP
                                     * server (imagine the case where the server has a single worker thread which is
                                     * occupied processing the primary search).
                                     * The best solution is probably to process
                                     * the primary search results in batches
                                     * The best solution is probably to process the primary search results in batches
                                     * using the paged results control.
                                     */
                                    final String id = nameStrategy.getResourceId(c, entry);
                                    final String revision = getRevisionFromEntry(entry);
                                    attributeMapper.read(c, new JsonPointer(), entry,
                                            new ResultHandler<JsonValue>() {
                                                @Override
                                                public void handleError(final ResourceException e) {
                                                    synchronized (sequenceLock) {
                                                        pendingResourceCount--;
                                                        completeIfNecessary(e);
                                                    }
                                                }
                                    attributeMapper.read(c, new JsonPointer(), entry, new ResultHandler<JsonValue>() {
                                        @Override
                                        public void handleError(final ResourceException e) {
                                            synchronized (sequenceLock) {
                                                pendingResourceCount--;
                                                completeIfNecessary(e);
                                            }
                                        }
                                                @Override
                                                public void handleResult(final JsonValue result) {
                                                    synchronized (sequenceLock) {
                                                        pendingResourceCount--;
                                                        if (!resultSent) {
                                                            h.handleResource(new Resource(id,
                                                                    revision, result));
                                                        }
                                                        completeIfNecessary();
                                                    }
                                        @Override
                                        public void handleResult(final JsonValue result) {
                                            synchronized (sequenceLock) {
                                                pendingResourceCount--;
                                                if (!resultSent) {
                                                    h.handleResource(new Resource(id, revision, result));
                                                }
                                            });
                                                completeIfNecessary();
                                            }
                                        }
                                    });
                                    return true;
                                }
                                @Override
                                public void handleErrorResult(final ErrorResultException error) {
                                    synchronized (sequenceLock) {
                                        completeIfNecessary(asResourceException(error));
                                    }
                                }
                                @Override
                                public boolean handleReference(final SearchResultReference reference) {
                                    // TODO: should this be classed as an error since rest2ldap
                                    // assumes entries are all colocated?
                                    return true;
                                }
                            }).onSuccess(new SuccessHandler<Result>() {
                                @Override
                                public void handleResult(final Result result) {
                                public void handleResult(Result result) {
                                    synchronized (sequenceLock) {
                                        if (request.getPageSize() > 0) {
                                            try {
                                                final SimplePagedResultsControl control = result.getControl(
                                                    SimplePagedResultsControl.DECODER, DECODE_OPTIONS);
                                                final SimplePagedResultsControl control =
                                                    result.getControl(SimplePagedResultsControl.DECODER,
                                                        DECODE_OPTIONS);
                                                if (control != null && !control.getCookie().isEmpty()) {
                                                    cookie = control.getCookie().toBase64String();
                                                }
@@ -495,46 +476,50 @@
                                        completeIfNecessary(SUCCESS);
                                    }
                                }
                                /*
                                 * This method must be invoked with the
                                 * sequenceLock held.
                                 */
                                private void completeIfNecessary(final ResourceException e) {
                                    if (pendingResult == null) {
                                        pendingResult = e;
                                    }
                                    completeIfNecessary();
                                }
                                /*
                                 * Close out the query result set if there are
                                 * no more pending resources and the LDAP result
                                 * has been received. This method must be
                                 * invoked with the sequenceLock held.
                                 */
                                private void completeIfNecessary() {
                                    if (pendingResourceCount == 0 && pendingResult != null
                                            && !resultSent) {
                                        if (pendingResult == SUCCESS) {
                                            h.handleResult(new QueryResult(cookie, -1));
                                        } else {
                                            h.handleError(pendingResult);
                                        }
                                        resultSent = true;
                            }).onFailure(new FailureHandler<ErrorResultException>() {
                                @Override
                                public void handleError(ErrorResultException error) {
                                    synchronized (sequenceLock) {
                                        completeIfNecessary(asResourceException(error));
                                    }
                                }
                            });
                        }
                    }
                    /**
                     * This method must be invoked with the sequenceLock held.
                     */
                    private void completeIfNecessary(final ResourceException e) {
                        if (pendingResult == null) {
                            pendingResult = e;
                        }
                        completeIfNecessary();
                    }
                    /**
                     * Close out the query result set if there are no more
                     * pending resources and the LDAP result has been received.
                     * This method must be invoked with the sequenceLock held.
                     */
                    private void completeIfNecessary() {
                        if (pendingResourceCount == 0 && pendingResult != null && !resultSent) {
                            if (pendingResult == SUCCESS) {
                                h.handleResult(new QueryResult(cookie, -1));
                            } else {
                                h.handleError(pendingResult);
                            }
                            resultSent = true;
                        }
                    }
                });
            }
        });
    }
    @Override
    public void readInstance(final ServerContext context, final String resourceId,
            final ReadRequest request, final ResultHandler<Resource> handler) {
    public void readInstance(final ServerContext context, final String resourceId, final ReadRequest request,
        final ResultHandler<Resource> handler) {
        final Context c = wrap(context);
        final ResultHandler<Resource> h = wrap(c, handler);
@@ -545,27 +530,27 @@
                // Do the search.
                final String[] attributes = getLDAPAttributes(c, request.getFields());
                final SearchRequest request =
                        nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId).addAttribute(
                                attributes);
                c.getConnection().searchSingleEntryAsync(request,
                        new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
                            @Override
                            public void handleErrorResult(final ErrorResultException error) {
                                h.handleError(asResourceException(error));
                            }
                    nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId).addAttribute(attributes);
                            @Override
                            public void handleResult(final SearchResultEntry entry) {
                                adaptEntry(c, entry, h);
                            }
                        });
            }
                c.getConnection().searchSingleEntryAsync(request).onSuccess(new SuccessHandler<SearchResultEntry>() {
                    @Override
                    public void handleResult(final SearchResultEntry entry) {
                        adaptEntry(c, entry, h);
                    }
                }).onFailure(new FailureHandler<ErrorResultException>() {
                    @Override
                    public void handleError(final ErrorResultException error) {
                        h.handleError(asResourceException(error));
                    }
                });
            };
        });
    }
    @Override
    public void updateInstance(final ServerContext context, final String resourceId,
            final UpdateRequest request, final ResultHandler<Resource> handler) {
    public void updateInstance(final ServerContext context, final String resourceId, final UpdateRequest request,
            final ResultHandler<Resource> handler) {
        /*
         * Update operations are a bit awkward because there is no direct
         * mapping to LDAP. We need to convert the update request into an LDAP
@@ -582,70 +567,53 @@
        c.run(h, new Runnable() {
            @Override
            public void run() {
                final String[] attributes =
                        getLDAPAttributes(c, Collections.<JsonPointer> emptyList());
                final SearchRequest searchRequest =
                        nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId).addAttribute(
                                attributes);
                c.getConnection().searchSingleEntryAsync(searchRequest,
                        new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
                            @Override
                            public void handleErrorResult(final ErrorResultException error) {
                                h.handleError(asResourceException(error));
                            }
                final String[] attributes = getLDAPAttributes(c, Collections.<JsonPointer> emptyList());
                final SearchRequest searchRequest = nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId)
                        .addAttribute(attributes);
                c.getConnection().searchSingleEntryAsync(searchRequest)
                        .onSuccess(new SuccessHandler<SearchResultEntry>() {
                            @Override
                            public void handleResult(final SearchResultEntry entry) {
                                try {
                                    // Fail-fast if there is a version mismatch.
                                    ensureMVCCVersionMatches(entry, request.getRevision());
                                    //  Create the modify request.
                                    final ModifyRequest modifyRequest =
                                            newModifyRequest(entry.getName());
                                    // Create the modify request.
                                    final ModifyRequest modifyRequest = newModifyRequest(entry.getName());
                                    if (config.readOnUpdatePolicy() == CONTROLS) {
                                        final String[] attributes =
                                                getLDAPAttributes(c, request.getFields());
                                        modifyRequest.addControl(PostReadRequestControl.newControl(
                                                false, attributes));
                                        final String[] attributes = getLDAPAttributes(c, request.getFields());
                                        modifyRequest.addControl(PostReadRequestControl.newControl(false, attributes));
                                    }
                                    if (config.usePermissiveModify()) {
                                        modifyRequest.addControl(PermissiveModifyRequestControl
                                                .newControl(true));
                                        modifyRequest.addControl(PermissiveModifyRequestControl.newControl(true));
                                    }
                                    addAssertionControl(modifyRequest, request.getRevision());
                                    /*
                                     * Determine the set of changes that need to
                                     * be performed.
                                     * Determine the set of changes that need to be performed.
                                     */
                                    attributeMapper.update(c, new JsonPointer(), entry, request
                                            .getNewContent(),
                                    attributeMapper.update(c, new JsonPointer(), entry, request.getNewContent(),
                                            new ResultHandler<List<Modification>>() {
                                                @Override
                                                public void handleError(
                                                        final ResourceException error) {
                                                public void handleError(final ResourceException error) {
                                                    h.handleError(error);
                                                }
                                                @Override
                                                public void handleResult(
                                                        final List<Modification> result) {
                                                public void handleResult(final List<Modification> result) {
                                                    // Perform the modify operation.
                                                    if (result.isEmpty()) {
                                                        /*
                                                         * No changes to be
                                                         * performed, so just
                                                         * return the entry that
                                                         * we read.
                                                         * No changes to be performed, so just return
                                                         * the entry that we read.
                                                         */
                                                        adaptEntry(c, entry, h);
                                                    } else {
                                                        modifyRequest.getModifications().addAll(
                                                                result);
                                                        c.getConnection().applyChangeAsync(
                                                                modifyRequest, null,
                                                                postUpdateHandler(c, h));
                                                        modifyRequest.getModifications().addAll(result);
                                                        c.getConnection().applyChangeAsync(modifyRequest)
                                                                .onSuccess(postUpdateSuccessHandler(c, h))
                                                                .onFailure(postUpdateFailureHandler(h));
                                                    }
                                                }
                                            });
@@ -653,13 +621,17 @@
                                    h.handleError(asResourceException(e));
                                }
                            }
                        }).onFailure(new FailureHandler<ErrorResultException>() {
                            @Override
                            public void handleError(final ErrorResultException error) {
                                h.handleError(asResourceException(error));
                            }
                        });
            }
        });
    }
    private void adaptEntry(final Context c, final Entry entry,
            final ResultHandler<Resource> handler) {
    private void adaptEntry(final Context c, final Entry entry, final ResultHandler<Resource> handler) {
        final String actualResourceId = nameStrategy.getResourceId(c, entry);
        final String revision = getRevisionFromEntry(entry);
        attributeMapper.read(c, new JsonPointer(), entry, transform(
@@ -695,13 +667,8 @@
                    // There's no point in doing a search because we already know the DN.
                    updateHandler.handleResult(searchRequest.getName());
                } else {
                    c.getConnection().searchSingleEntryAsync(searchRequest,
                            new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
                                @Override
                                public void handleErrorResult(final ErrorResultException error) {
                                    updateHandler.handleError(asResourceException(error));
                                }
                    c.getConnection().searchSingleEntryAsync(searchRequest)
                            .onSuccess(new SuccessHandler<SearchResultEntry>() {
                                @Override
                                public void handleResult(final SearchResultEntry entry) {
                                    try {
@@ -714,6 +681,11 @@
                                        updateHandler.handleError(asResourceException(e));
                                    }
                                }
                            }).onFailure(new FailureHandler<ErrorResultException>() {
                                @Override
                                public void handleError(final ErrorResultException error) {
                                    updateHandler.handleError(asResourceException(error));
                                }
                            });
                }
            }
@@ -727,21 +699,18 @@
        }
    }
    private void ensureMVCCVersionMatches(final Entry entry, final String expectedRevision)
            throws ResourceException {
    private void ensureMVCCVersionMatches(final Entry entry, final String expectedRevision) throws ResourceException {
        if (expectedRevision != null) {
            ensureMVCCSupported();
            final String actualRevision = entry.parseAttribute(etagAttribute).asString();
            if (actualRevision == null) {
                throw new PreconditionFailedException(i18n(
                        "The resource could not be accessed because it did not contain any "
                                + "version information, when the version '%s' was expected",
                        expectedRevision));
                                + "version information, when the version '%s' was expected", expectedRevision));
            } else if (!expectedRevision.equals(actualRevision)) {
                throw new PreconditionFailedException(i18n(
                        "The resource could not be accessed because the expected version '%s' "
                                + "does not match the current version '%s'", expectedRevision,
                        actualRevision));
                                + "does not match the current version '%s'", expectedRevision, actualRevision));
            }
        }
    }
@@ -785,42 +754,55 @@
        return requestedLDAPAttributes.toArray(new String[requestedLDAPAttributes.size()]);
    }
    private void getLDAPFilter(final Context c, final QueryFilter queryFilter,
            final ResultHandler<Filter> h) {
    private void getLDAPFilter(final Context c, final QueryFilter queryFilter, final ResultHandler<Filter> h) {
        final QueryFilterVisitor<Void, ResultHandler<Filter>> visitor =
                new QueryFilterVisitor<Void, ResultHandler<Filter>>() {
                    @Override
                    public Void visitAndFilter(final ResultHandler<Filter> p,
                            final List<QueryFilter> subFilters) {
                        final ResultHandler<Filter> handler =
                                accumulate(subFilters.size(), transform(
                                        new Function<List<Filter>, Filter, Void>() {
                                            @Override
                                            public Filter apply(final List<Filter> value,
                                                    final Void p) {
                                                // Check for unmapped filter components and optimize.
                                                final Iterator<Filter> i = value.iterator();
                                                while (i.hasNext()) {
                                                    final Filter f = i.next();
                                                    if (f == alwaysFalse()) {
                                                        return alwaysFalse();
                                                    } else if (f == alwaysTrue()) {
                                                        i.remove();
                                                    }
                                                }
                                                switch (value.size()) {
                                                case 0:
                                                    return alwaysTrue();
                                                case 1:
                                                    return value.get(0);
                                                default:
                                                    return Filter.and(value);
                                                }
                                            }
                                        }, p));
                    public Void visitAndFilter(final ResultHandler<Filter> p, final List<QueryFilter> subFilters) {
                        List<Promise<Filter, ResourceException>> promises =
                                new ArrayList<Promise<Filter, ResourceException>>(subFilters.size());
                        for (final QueryFilter subFilter : subFilters) {
                            final ResultHandlerFromPromise<Filter> handler = new ResultHandlerFromPromise<Filter>();
                            subFilter.accept(this, handler);
                            promises.add(handler.promise);
                        }
                        Promises.when(promises)
                                .then(new org.forgerock.util.promise.Function<List<Filter>, Filter,
                                        ResourceException>() {
                                    @Override
                                    public Filter apply(final List<Filter> value) {
                                        // Check for unmapped filter components and optimize.
                                        final Iterator<Filter> i = value.iterator();
                                        while (i.hasNext()) {
                                            final Filter f = i.next();
                                            if (f == alwaysFalse()) {
                                                return alwaysFalse();
                                            } else if (f == alwaysTrue()) {
                                                i.remove();
                                            }
                                        }
                                        switch (value.size()) {
                                        case 0:
                                            return alwaysTrue();
                                        case 1:
                                            return value.get(0);
                                        default:
                                            return Filter.and(value);
                                        }
                                    }
                                }).onSuccess(new SuccessHandler<Filter>() {
                                    @Override
                                    public void handleResult(Filter result) {
                                        p.handleResult(result);
                                    }
                                }).onFailure(new FailureHandler<ResourceException>() {
                                    @Override
                                    public void handleError(ResourceException error) {
                                        p.handleError(error);
                                    }
                                });
                        return null;
                    }
@@ -907,37 +889,51 @@
                    }
                    @Override
                    public Void visitOrFilter(final ResultHandler<Filter> p,
                            final List<QueryFilter> subFilters) {
                        final ResultHandler<Filter> handler =
                                accumulate(subFilters.size(), transform(
                                        new Function<List<Filter>, Filter, Void>() {
                                            @Override
                                            public Filter apply(final List<Filter> value,
                                                    final Void p) {
                                                // Check for unmapped filter components and optimize.
                                                final Iterator<Filter> i = value.iterator();
                                                while (i.hasNext()) {
                                                    final Filter f = i.next();
                                                    if (f == alwaysFalse()) {
                                                        i.remove();
                                                    } else if (f == alwaysTrue()) {
                                                        return alwaysTrue();
                                                    }
                                                }
                                                switch (value.size()) {
                                                case 0:
                                                    return alwaysFalse();
                                                case 1:
                                                    return value.get(0);
                                                default:
                                                    return Filter.or(value);
                                                }
                                            }
                                        }, p));
                    public Void visitOrFilter(final ResultHandler<Filter> p, final List<QueryFilter> subFilters) {
                        List<Promise<Filter, ResourceException>> promises =
                                new ArrayList<Promise<Filter, ResourceException>>(subFilters.size());
                        for (final QueryFilter subFilter : subFilters) {
                            final ResultHandlerFromPromise<Filter> handler = new ResultHandlerFromPromise<Filter>();
                            subFilter.accept(this, handler);
                            promises.add(handler.promise);
                        }
                        Promises.when(promises)
                                .then(new org.forgerock.util.promise.Function<List<Filter>, Filter,
                                        ResourceException>() {
                                    @Override
                                    public Filter apply(final List<Filter> value) {
                                        // Check for unmapped filter components and optimize.
                                        final Iterator<Filter> i = value.iterator();
                                        while (i.hasNext()) {
                                            final Filter f = i.next();
                                            if (f == alwaysFalse()) {
                                                i.remove();
                                            } else if (f == alwaysTrue()) {
                                                return alwaysTrue();
                                            }
                                        }
                                        switch (value.size()) {
                                        case 0:
                                            return alwaysFalse();
                                        case 1:
                                            return value.get(0);
                                        default:
                                            return Filter.or(value);
                                        }
                                    }
                                }).onSuccess(new SuccessHandler<Filter>() {
                                    @Override
                                    public void handleResult(Filter result) {
                                        p.handleResult(result);
                                    }
                                }).onFailure(new FailureHandler<ResourceException>() {
                                    @Override
                                    public void handleError(ResourceException error) {
                                        p.handleError(error);
                                    }
                                });
                        return null;
                    }
@@ -959,8 +955,7 @@
                };
        /*
         * Note that the returned LDAP filter may be null if it could not be
         * mapped by any attribute mappers.
         * Note that the returned LDAP filter may be null if it could not be mapped by any attribute mappers.
         */
        queryFilter.accept(visitor, h);
    }
@@ -969,14 +964,9 @@
        return etagAttribute != null ? entry.parseAttribute(etagAttribute).asString() : null;
    }
    private org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry> postEmptyPatchHandler(
            final Context c, final PatchRequest request, final ResultHandler<Resource> h) {
        return new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
            @Override
            public void handleErrorResult(final ErrorResultException error) {
                h.handleError(asResourceException(error));
            }
    private SuccessHandler<SearchResultEntry> postEmptyPatchSuccessHandler(final Context c,
            final PatchRequest request, final ResultHandler<Resource> h) {
        return new SuccessHandler<SearchResultEntry>() {
            @Override
            public void handleResult(final SearchResultEntry entry) {
                try {
@@ -990,29 +980,30 @@
        };
    }
    private org.forgerock.opendj.ldap.ResultHandler<Result> postUpdateHandler(final Context c,
            final ResultHandler<Resource> handler) {
        // The handler which will be invoked for the LDAP add result.
        return new org.forgerock.opendj.ldap.ResultHandler<Result>() {
    private FailureHandler<ErrorResultException> postEmptyPatchFailureHandler(final ResultHandler<Resource> h) {
        return new FailureHandler<ErrorResultException>() {
            @Override
            public void handleErrorResult(final ErrorResultException error) {
                handler.handleError(asResourceException(error));
            public void handleError(final ErrorResultException error) {
                h.handleError(asResourceException(error));
            }
        };
    }
    private SuccessHandler<Result> postUpdateSuccessHandler(final Context c, final ResultHandler<Resource> handler) {
        // The handler which will be invoked for the LDAP add result.
        return new SuccessHandler<Result>() {
            @Override
            public void handleResult(final Result result) {
                // FIXME: handle USE_SEARCH policy.
                Entry entry;
                try {
                    final PostReadResponseControl postReadControl =
                            result.getControl(PostReadResponseControl.DECODER, config
                                    .decodeOptions());
                        result.getControl(PostReadResponseControl.DECODER, config.decodeOptions());
                    if (postReadControl != null) {
                        entry = postReadControl.getEntry();
                    } else {
                        final PreReadResponseControl preReadControl =
                                result.getControl(PreReadResponseControl.DECODER, config
                                        .decodeOptions());
                            result.getControl(PreReadResponseControl.DECODER, config.decodeOptions());
                        if (preReadControl != null) {
                            entry = preReadControl.getEntry();
                        } else {
@@ -1026,8 +1017,7 @@
                if (entry != null) {
                    adaptEntry(c, entry, handler);
                } else {
                    final Resource resource =
                            new Resource(null, null, new JsonValue(Collections.emptyMap()));
                    final Resource resource = new Resource(null, null, new JsonValue(Collections.emptyMap()));
                    handler.handleResult(resource);
                }
            }
@@ -1035,6 +1025,16 @@
        };
    }
    private FailureHandler<ErrorResultException> postUpdateFailureHandler(final ResultHandler<Resource> handler) {
        // The handler which will be invoked for the LDAP add result.
        return new FailureHandler<ErrorResultException>() {
            @Override
            public void handleError(final ErrorResultException error) {
                handler.handleError(asResourceException(error));
            }
        };
    }
    private QueryResultHandler wrap(final Context c, final QueryResultHandler handler) {
        return new QueryResultHandler() {
            @Override
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -11,18 +11,10 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2012-2013 ForgeRock AS.
 * Copyright 2012-2014 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.transform;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
@@ -54,13 +46,19 @@
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.FailureHandler;
import org.forgerock.util.promise.SuccessHandler;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
import static org.forgerock.opendj.rest2ldap.Utils.*;
/**
 * An attribute mapper which provides a mapping from a JSON value to a single DN
 * valued LDAP attribute.
 */
public final class ReferenceAttributeMapper extends
        AbstractLDAPAttributeMapper<ReferenceAttributeMapper> {
public final class ReferenceAttributeMapper extends AbstractLDAPAttributeMapper<ReferenceAttributeMapper> {
    /**
     * The maximum number of candidate references to allow in search filters.
     */
@@ -73,7 +71,7 @@
    private SearchScope scope = SearchScope.WHOLE_SUBTREE;
    ReferenceAttributeMapper(final AttributeDescription ldapAttributeName, final DN baseDN,
            final AttributeDescription primaryKey, final AttributeMapper mapper) {
        final AttributeDescription primaryKey, final AttributeMapper mapper) {
        super(ldapAttributeName);
        this.baseDN = baseDN;
        this.primaryKey = primaryKey;
@@ -127,65 +125,65 @@
    }
    @Override
    void getLDAPFilter(final Context c, final JsonPointer path, final JsonPointer subPath,
            final FilterType type, final String operator, final Object valueAssertion,
            final ResultHandler<Filter> h) {
    void getLDAPFilter(final Context c, final JsonPointer path, final JsonPointer subPath, final FilterType type,
        final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
        // Construct a filter which can be used to find referenced resources.
        mapper.getLDAPFilter(c, path, subPath, type, operator, valueAssertion,
                new ResultHandler<Filter>() {
        mapper.getLDAPFilter(c, path, subPath, type, operator, valueAssertion, new ResultHandler<Filter>() {
            @Override
            public void handleError(final ResourceException error) {
                h.handleError(error); // Propagate.
            }
            @Override
            public void handleResult(final Filter result) {
                // Search for all referenced entries and construct a filter.
                final SearchRequest request = createSearchRequest(result);
                final List<Filter> subFilters = new LinkedList<Filter>();
                final FailureHandler<ErrorResultException> failureHandler =
                    new FailureHandler<ErrorResultException>() {
                        @Override
                        public void handleError(ErrorResultException error) {
                            h.handleError(asResourceException(error)); // Propagate.
                        }
                    };
                c.getConnection().searchAsync(request, new SearchResultHandler() {
                    @Override
                    public void handleError(final ResourceException error) {
                        h.handleError(error); // Propagate.
                    public boolean handleEntry(final SearchResultEntry entry) {
                        if (subFilters.size() < SEARCH_MAX_CANDIDATES) {
                            subFilters.add(Filter.equality(ldapAttributeName.toString(), entry.getName()));
                            return true;
                        } else {
                            // No point in continuing - maximum candidates reached.
                            return false;
                        }
                    }
                    @Override
                    public void handleResult(final Filter result) {
                        // Search for all referenced entries and construct a filter.
                        final SearchRequest request = createSearchRequest(result);
                        c.getConnection().searchAsync(request, null, new SearchResultHandler() {
                            final List<Filter> subFilters = new LinkedList<Filter>();
                            @Override
                            public boolean handleEntry(final SearchResultEntry entry) {
                                if (subFilters.size() < SEARCH_MAX_CANDIDATES) {
                                    subFilters.add(Filter.equality(ldapAttributeName.toString(),
                                            entry.getName()));
                                    return true;
                                } else {
                                    // No point in continuing - maximum candidates reached.
                                    return false;
                                }
                            }
                            @Override
                            public void handleErrorResult(final ErrorResultException error) {
                                h.handleError(asResourceException(error)); // Propagate.
                            }
                            @Override
                            public boolean handleReference(final SearchResultReference reference) {
                                // Ignore references.
                                return true;
                            }
                            @Override
                            public void handleResult(final Result result) {
                                if (subFilters.size() >= SEARCH_MAX_CANDIDATES) {
                                    handleErrorResult(newErrorResult(ResultCode.ADMIN_LIMIT_EXCEEDED));
                                } else if (subFilters.size() == 1) {
                                    h.handleResult(subFilters.get(0));
                                } else {
                                    h.handleResult(Filter.or(subFilters));
                                }
                            }
                        });
                    public boolean handleReference(final SearchResultReference reference) {
                        // Ignore references.
                        return true;
                    }
                });
                }).onSuccess(new SuccessHandler<Result>() {
                    @Override
                    public void handleResult(Result result) {
                        if (subFilters.size() >= SEARCH_MAX_CANDIDATES) {
                            failureHandler.handleError(newErrorResult(ResultCode.ADMIN_LIMIT_EXCEEDED));
                        } else if (subFilters.size() == 1) {
                            h.handleResult(subFilters.get(0));
                        } else {
                            h.handleResult(Filter.or(subFilters));
                        }
                    }
                }).onFailure(failureHandler);
            }
        });
    }
    @Override
    void getNewLDAPAttributes(final Context c, final JsonPointer path,
            final List<Object> newValues, final ResultHandler<Attribute> h) {
    void getNewLDAPAttributes(final Context c, final JsonPointer path, final List<Object> newValues,
        final ResultHandler<Attribute> h) {
        /*
         * For each value use the subordinate mapper to obtain the LDAP primary
         * key, the perform a search for each one to find the corresponding
@@ -193,8 +191,7 @@
         */
        final Attribute newLDAPAttribute = new LinkedAttribute(ldapAttributeName);
        final AtomicInteger pendingSearches = new AtomicInteger(newValues.size());
        final AtomicReference<ResourceException> exception =
                new AtomicReference<ResourceException>();
        final AtomicReference<ResourceException> exception = new AtomicReference<ResourceException>();
        for (final Object value : newValues) {
            mapper.create(c, path, new JsonValue(value), new ResultHandler<List<Attribute>>() {
@@ -216,17 +213,15 @@
                    if (primaryKeyAttribute == null || primaryKeyAttribute.isEmpty()) {
                        h.handleError(new BadRequestException(i18n(
                                "The request cannot be processed because the reference "
                                        + "field '%s' contains a value which does not contain "
                                        + "a primary key", path)));
                            "The request cannot be processed because the reference "
                                + "field '%s' contains a value which does not contain " + "a primary key", path)));
                        return;
                    }
                    if (primaryKeyAttribute.size() > 1) {
                        h.handleError(new BadRequestException(i18n(
                                "The request cannot be processed because the reference "
                                        + "field '%s' contains a value which contains multiple "
                                        + "primary keys", path)));
                            "The request cannot be processed because the reference "
                                + "field '%s' contains a value which contains multiple " + "primary keys", path)));
                        return;
                    }
@@ -234,45 +229,39 @@
                    final ByteString primaryKeyValue = primaryKeyAttribute.firstValue();
                    final Filter filter = Filter.equality(primaryKey.toString(), primaryKeyValue);
                    final SearchRequest search = createSearchRequest(filter);
                    c.getConnection().searchSingleEntryAsync(search,
                            new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
                    c.getConnection().searchSingleEntryAsync(search).onSuccess(new SuccessHandler<SearchResultEntry>() {
                        @Override
                        public void handleResult(final SearchResultEntry result) {
                            synchronized (newLDAPAttribute) {
                                newLDAPAttribute.add(result.getName());
                            }
                            completeIfNecessary();
                        }
                    }).onFailure(new FailureHandler<ErrorResultException>() {
                        @Override
                        public void handleError(final ErrorResultException error) {
                            ResourceException re;
                            try {
                                throw error;
                            } catch (final EntryNotFoundException e) {
                                re =
                                    new BadRequestException(i18n("The request cannot be processed "
                                        + "because the resource '%s' " + "referenced in field '%s' does "
                                        + "not exist", primaryKeyValue.toString(), path));
                            } catch (final MultipleEntriesFoundException e) {
                                re =
                                    new BadRequestException(i18n(
                                        "The request cannot be processed " + "because the resource '%s' "
                                            + "referenced in field '%s' is " + "ambiguous",
                                        primaryKeyValue.toString(), path));
                            } catch (final ErrorResultException e) {
                                re = asResourceException(e);
                            }
                            exception.compareAndSet(null, re);
                            completeIfNecessary();
                        }
                                @Override
                                public void handleErrorResult(final ErrorResultException error) {
                                    ResourceException re;
                                    try {
                                        throw error;
                                    } catch (final EntryNotFoundException e) {
                                        re =
                                                new BadRequestException(i18n(
                                                        "The request cannot be processed "
                                                                + "because the resource '%s' "
                                                                + "referenced in field '%s' does "
                                                                + "not exist", primaryKeyValue
                                                                .toString(), path));
                                    } catch (final MultipleEntriesFoundException e) {
                                        re =
                                                new BadRequestException(i18n(
                                                        "The request cannot be processed "
                                                                + "because the resource '%s' "
                                                                + "referenced in field '%s' is "
                                                                + "ambiguous", primaryKeyValue
                                                                .toString(), path));
                                    } catch (final ErrorResultException e) {
                                        re = asResourceException(e);
                                    }
                                    exception.compareAndSet(null, re);
                                    completeIfNecessary();
                                }
                                @Override
                                public void handleResult(final SearchResultEntry result) {
                                    synchronized (newLDAPAttribute) {
                                        newLDAPAttribute.add(result.getName());
                                    }
                                    completeIfNecessary();
                                }
                            });
                    });
                }
                private void completeIfNecessary() {
@@ -294,8 +283,7 @@
    }
    @Override
    void read(final Context c, final JsonPointer path, final Entry e,
            final ResultHandler<JsonValue> h) {
    void read(final Context c, final JsonPointer path, final Entry e, final ResultHandler<JsonValue> h) {
        final Attribute attribute = e.getAttribute(ldapAttributeName);
        if (attribute == null || attribute.isEmpty()) {
            h.handleResult(null);
@@ -309,30 +297,27 @@
            }
        } else {
            try {
                final Set<DN> dns =
                        attribute.parse().usingSchema(c.getConfig().schema()).asSetOfDN();
                final Set<DN> dns = attribute.parse().usingSchema(c.getConfig().schema()).asSetOfDN();
                final ResultHandler<JsonValue> handler =
                        accumulate(dns.size(), transform(
                                new Function<List<JsonValue>, JsonValue, Void>() {
                                    @Override
                                    public JsonValue apply(final List<JsonValue> value, final Void p) {
                                        if (value.isEmpty()) {
                                            /*
                                             * No values, so omit the entire
                                             * JSON object from the resource.
                                             */
                                            return null;
                                        } else {
                                            // Combine values into a single JSON array.
                                            final List<Object> result =
                                                    new ArrayList<Object>(value.size());
                                            for (final JsonValue e : value) {
                                                result.add(e.getObject());
                                            }
                                            return new JsonValue(result);
                                        }
                                    }
                                }, h));
                    accumulate(dns.size(), transform(new Function<List<JsonValue>, JsonValue, Void>() {
                        @Override
                        public JsonValue apply(final List<JsonValue> value, final Void p) {
                            if (value.isEmpty()) {
                                /*
                                 * No values, so omit the entire JSON object
                                 * from the resource.
                                 */
                                return null;
                            } else {
                                // Combine values into a single JSON array.
                                final List<Object> result = new ArrayList<Object>(value.size());
                                for (final JsonValue e : value) {
                                    result.add(e.getObject());
                                }
                                return new JsonValue(result);
                            }
                        }
                    }, h));
                for (final DN dn : dns) {
                    readEntry(c, path, dn, handler);
                }
@@ -349,14 +334,18 @@
    }
    private void readEntry(final Context c, final JsonPointer path, final DN dn,
            final ResultHandler<JsonValue> handler) {
        final ResultHandler<JsonValue> handler) {
        final Set<String> requestedLDAPAttributes = new LinkedHashSet<String>();
        mapper.getLDAPAttributes(c, path, new JsonPointer(), requestedLDAPAttributes);
        c.getConnection().readEntryAsync(dn, requestedLDAPAttributes,
                new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
        c.getConnection().readEntryAsync(dn, requestedLDAPAttributes)
                .onSuccess(new SuccessHandler<SearchResultEntry>() {
                    @Override
                    public void handleErrorResult(final ErrorResultException error) {
                    public void handleResult(final SearchResultEntry result) {
                        mapper.read(c, path, result, handler);
                    }
                }).onFailure(new FailureHandler<ErrorResultException>() {
                    @Override
                    public void handleError(final ErrorResultException error) {
                        if (!(error instanceof EntryNotFoundException)) {
                            handler.handleError(asResourceException(error));
                        } else {
@@ -367,11 +356,6 @@
                            handler.handleResult(null);
                        }
                    }
                    @Override
                    public void handleResult(final SearchResultEntry result) {
                        mapper.read(c, path, result, handler);
                    }
                });
    }
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.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 ForgeRock AS.
 * Copyright 2013-2014 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
@@ -779,11 +779,11 @@
            @Override
            public void handleSearch(RequestContext requestContext, SearchRequest request,
                    IntermediateResponseHandler intermediateResponseHandler,
                    SearchResultHandler resultHandler) {
                IntermediateResponseHandler intermediateResponseHandler, SearchResultHandler entryHandler,
                ResultHandler<Result> resultHandler) {
                requests.add(request);
                handler.handleSearch(requestContext, request, intermediateResponseHandler,
                        resultHandler);
                handler.handleSearch(requestContext, request, intermediateResponseHandler, entryHandler,
                    resultHandler);
            }
        };
opendj-server2x-adapter/src/main/java/org/forgerock/opendj/adapter/server2x/Adapters.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2013 ForgeRock AS.
 *      Copyright 2013-2014 ForgeRock AS.
 */
package org.forgerock.opendj.adapter.server2x;
@@ -37,10 +37,8 @@
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.requests.AddRequest;
@@ -62,6 +60,7 @@
import org.forgerock.opendj.ldap.responses.GenericExtendedResult;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.util.promise.Promise;
import org.opends.server.core.AddOperation;
import org.opends.server.core.BindOperation;
import org.opends.server.core.CompareOperation;
@@ -78,10 +77,9 @@
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import com.forgerock.opendj.util.CompletedFutureResult;
import static org.forgerock.opendj.adapter.server2x.Converters.*;
import static org.forgerock.opendj.ldap.ByteString.*;
import static org.forgerock.util.promise.Promises.*;
/**
 * This class provides a connection factory and an adapter for the OpenDJ 2.x
@@ -141,8 +139,7 @@
     * @return A new SDK connection factory.
     */
    public static ConnectionFactory newConnectionFactory(final InternalClientConnection icc) {
        final Connection connection = newConnection(icc);
        ConnectionFactory factory = new ConnectionFactory() {
        return new ConnectionFactory() {
            @Override
            public void close() {
@@ -150,20 +147,16 @@
            }
            @Override
            public FutureResult<Connection> getConnectionAsync(
                    ResultHandler<? super Connection> handler) {
                if (handler != null) {
                    handler.handleResult(connection);
                } // TODO change the path...
                return new CompletedFutureResult<Connection>(connection);
            public Promise<Connection, ErrorResultException> getConnectionAsync() {
                // TODO change the path...
                return newSuccessfulPromise(newConnection(icc));
            }
            @Override
            public Connection getConnection() throws ErrorResultException {
                return connection;
                return newConnection(icc);
            }
        };
        return factory;
    }
    /**
opendj-server3x-adapter/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java
@@ -38,10 +38,8 @@
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.requests.AddRequest;
@@ -63,6 +61,7 @@
import org.forgerock.opendj.ldap.responses.GenericExtendedResult;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.util.promise.Promise;
import org.opends.server.core.AddOperation;
import org.opends.server.core.BindOperation;
import org.opends.server.core.CompareOperation;
@@ -78,10 +77,9 @@
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import com.forgerock.opendj.util.CompletedFutureResult;
import static org.forgerock.opendj.adapter.server3x.Converters.*;
import static org.forgerock.opendj.ldap.ByteString.*;
import static org.forgerock.util.promise.Promises.*;
/**
 * This class provides a connection factory and an adapter for the OpenDJ 2.x
@@ -141,8 +139,7 @@
     * @return A new SDK connection factory.
     */
    public static ConnectionFactory newConnectionFactory(final InternalClientConnection icc) {
        final Connection connection = newConnection(icc);
        ConnectionFactory factory = new ConnectionFactory() {
        return new ConnectionFactory() {
            @Override
            public void close() {
@@ -150,20 +147,16 @@
            }
            @Override
            public FutureResult<Connection> getConnectionAsync(
                    ResultHandler<? super Connection> handler) {
                if (handler != null) {
                    handler.handleResult(connection);
                } // TODO change the path...
                return new CompletedFutureResult<Connection>(connection);
            public Promise<Connection, ErrorResultException> getConnectionAsync() {
                // TODO change the path...
                return newSuccessfulPromise(newConnection(icc));
            }
            @Override
            public Connection getConnection() throws ErrorResultException {
                return connection;
                return newConnection(icc);
            }
        };
        return factory;
    }
    /**
pom.xml
@@ -438,7 +438,7 @@
      <dependency>
        <groupId>org.forgerock.commons</groupId>
        <artifactId>forgerock-util</artifactId>
        <version>1.3.0-SNAPSHOT</version>
        <version>1.3.5-SNAPSHOT</version>
      </dependency>
    </dependencies>
  </dependencyManagement>