Package org.apache.directory.ldap.client.api

Source Code of org.apache.directory.ldap.client.api.LdapConnection

/*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*    http://www.apache.org/licenses/LICENSE-2.0
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/
package org.apache.directory.ldap.client.api;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.naming.InvalidNameException;
import javax.net.ssl.SSLContext;

import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
import org.apache.directory.ldap.client.api.exception.LdapException;
import org.apache.directory.ldap.client.api.future.AddFuture;
import org.apache.directory.ldap.client.api.future.BindFuture;
import org.apache.directory.ldap.client.api.future.CompareFuture;
import org.apache.directory.ldap.client.api.future.DeleteFuture;
import org.apache.directory.ldap.client.api.future.ExtendedFuture;
import org.apache.directory.ldap.client.api.future.ModifyDnFuture;
import org.apache.directory.ldap.client.api.future.ModifyFuture;
import org.apache.directory.ldap.client.api.future.ResponseFuture;
import org.apache.directory.ldap.client.api.future.SearchFuture;
import org.apache.directory.ldap.client.api.listener.DeleteListener;
import org.apache.directory.ldap.client.api.message.AbandonRequest;
import org.apache.directory.ldap.client.api.message.AbstractMessage;
import org.apache.directory.ldap.client.api.message.AddRequest;
import org.apache.directory.ldap.client.api.message.AddResponse;
import org.apache.directory.ldap.client.api.message.BindRequest;
import org.apache.directory.ldap.client.api.message.BindResponse;
import org.apache.directory.ldap.client.api.message.CompareRequest;
import org.apache.directory.ldap.client.api.message.CompareResponse;
import org.apache.directory.ldap.client.api.message.DeleteRequest;
import org.apache.directory.ldap.client.api.message.DeleteResponse;
import org.apache.directory.ldap.client.api.message.ExtendedIntermediateResponse;
import org.apache.directory.ldap.client.api.message.ExtendedRequest;
import org.apache.directory.ldap.client.api.message.ExtendedResponse;
import org.apache.directory.ldap.client.api.message.IntermediateResponse;
import org.apache.directory.ldap.client.api.message.LdapResult;
import org.apache.directory.ldap.client.api.message.ModifyDnRequest;
import org.apache.directory.ldap.client.api.message.ModifyDnResponse;
import org.apache.directory.ldap.client.api.message.ModifyRequest;
import org.apache.directory.ldap.client.api.message.ModifyResponse;
import org.apache.directory.ldap.client.api.message.Referral;
import org.apache.directory.ldap.client.api.message.Response;
import org.apache.directory.ldap.client.api.message.SearchIntermediateResponse;
import org.apache.directory.ldap.client.api.message.SearchRequest;
import org.apache.directory.ldap.client.api.message.SearchResponse;
import org.apache.directory.ldap.client.api.message.SearchResultDone;
import org.apache.directory.ldap.client.api.message.SearchResultEntry;
import org.apache.directory.ldap.client.api.message.SearchResultReference;
import org.apache.directory.ldap.client.api.protocol.LdapProtocolCodecFactory;
import org.apache.directory.shared.asn1.ber.IAsn1Container;
import org.apache.directory.shared.asn1.codec.DecoderException;
import org.apache.directory.shared.asn1.primitives.OID;
import org.apache.directory.shared.ldap.codec.LdapMessageCodec;
import org.apache.directory.shared.ldap.codec.LdapMessageContainer;
import org.apache.directory.shared.ldap.codec.LdapResultCodec;
import org.apache.directory.shared.ldap.codec.LdapTransformer;
import org.apache.directory.shared.ldap.codec.abandon.AbandonRequestCodec;
import org.apache.directory.shared.ldap.codec.add.AddRequestCodec;
import org.apache.directory.shared.ldap.codec.add.AddResponseCodec;
import org.apache.directory.shared.ldap.codec.bind.BindRequestCodec;
import org.apache.directory.shared.ldap.codec.bind.BindResponseCodec;
import org.apache.directory.shared.ldap.codec.bind.LdapAuthentication;
import org.apache.directory.shared.ldap.codec.bind.SaslCredentials;
import org.apache.directory.shared.ldap.codec.bind.SimpleAuthentication;
import org.apache.directory.shared.ldap.codec.compare.CompareRequestCodec;
import org.apache.directory.shared.ldap.codec.compare.CompareResponseCodec;
import org.apache.directory.shared.ldap.codec.controls.ControlImpl;
import org.apache.directory.shared.ldap.codec.del.DelRequestCodec;
import org.apache.directory.shared.ldap.codec.del.DelResponseCodec;
import org.apache.directory.shared.ldap.codec.extended.ExtendedRequestCodec;
import org.apache.directory.shared.ldap.codec.extended.ExtendedResponseCodec;
import org.apache.directory.shared.ldap.codec.intermediate.IntermediateResponseCodec;
import org.apache.directory.shared.ldap.codec.modify.ModifyRequestCodec;
import org.apache.directory.shared.ldap.codec.modify.ModifyResponseCodec;
import org.apache.directory.shared.ldap.codec.modifyDn.ModifyDNRequestCodec;
import org.apache.directory.shared.ldap.codec.modifyDn.ModifyDNResponseCodec;
import org.apache.directory.shared.ldap.codec.search.Filter;
import org.apache.directory.shared.ldap.codec.search.SearchRequestCodec;
import org.apache.directory.shared.ldap.codec.search.SearchResultDoneCodec;
import org.apache.directory.shared.ldap.codec.search.SearchResultEntryCodec;
import org.apache.directory.shared.ldap.codec.search.SearchResultReferenceCodec;
import org.apache.directory.shared.ldap.codec.unbind.UnBindRequestCodec;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.cursor.Cursor;
import org.apache.directory.shared.ldap.entry.Entry;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.FilterParser;
import org.apache.directory.shared.ldap.filter.SearchScope;
import org.apache.directory.shared.ldap.message.AliasDerefMode;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.message.control.Control;
import org.apache.directory.shared.ldap.name.DN;
import org.apache.directory.shared.ldap.name.RDN;
import org.apache.directory.shared.ldap.schema.SchemaManager;
import org.apache.directory.shared.ldap.schema.loader.ldif.JarLdifSchemaLoader;
import org.apache.directory.shared.ldap.schema.manager.impl.DefaultSchemaManager;
import org.apache.directory.shared.ldap.util.LdapURL;
import org.apache.directory.shared.ldap.util.StringTools;
import org.apache.mina.core.filterchain.IoFilter;
import org.apache.mina.core.future.CloseFuture;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.ssl.SslFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* This class is the base for every operations sent or received to and
* from a LDAP server.
*
* A connection instance is necessary to send requests to the server. The connection
* is valid until either the client closes it, the server closes it or the
* client does an unbind.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public class LdapConnection extends IoHandlerAdapter
{

    /** logger for reporting errors that might not be handled properly upstream */
    private static final Logger LOG = LoggerFactory.getLogger( LdapConnection.class );

    private static final String LDAP_RESPONSE = "LdapReponse";

    /** The timeout used for response we are waiting for */
    private long timeOut = LdapConnectionConfig.DEFAULT_TIMEOUT;

    /** configuration object for the connection */
    private LdapConnectionConfig config = new LdapConnectionConfig();

    /** The connector open with the remote server */
    private IoConnector connector;

    /** A flag set to true when we used a local connector */
    private boolean localConnector;

    /** The Ldap codec */
    private IoFilter ldapProtocolFilter = new ProtocolCodecFilter( new LdapProtocolCodecFactory() );

    /** 
     * The created session, created when we open a connection with
     * the Ldap server.
     */
    private IoSession ldapSession;

    /** A Message ID which is incremented for each operation */
    private AtomicInteger messageId;

    /** a map to hold the ResponseFutures for all operations */
    private Map<Integer, ResponseFuture<? extends Response>> futureMap = new ConcurrentHashMap<Integer, ResponseFuture<? extends Response>>();

    /** list of controls supported by the server */
    private List<String> supportedControls;

    /** The ROOT DSE entry */
    private Entry rootDSE;
   
    /** A flag indicating that the BindRequest has been issued and successfully authenticated the user */
    private AtomicBoolean authenticated = new AtomicBoolean( false );
   
    /** A flag indicating that the connection is connected or not */
    private AtomicBoolean connected = new AtomicBoolean( false );
   
    /** the schema manager */
    private SchemaManager schemaManager;

    // ~~~~~~~~~~~~~~~~~ common error messages ~~~~~~~~~~~~~~~~~~~~~~~~~~

    private static final String OPERATION_CANCELLED = "Operation would have been cancelled";

    static final String TIME_OUT_ERROR = "TimeOut occured";

    static final String NO_RESPONSE_ERROR = "The response queue has been emptied, no response was found.";

    private static final String COMPARE_FAILED = "Failed to perform compare operation";


    //--------------------------- Helper methods ---------------------------//
    /**
     * Check if we are connected
     *
     * @return <code>true</code> if we are connected.
     */
    public boolean isConnected()
    {
        return ( ldapSession != null ) && connected.get();
    }


    /**
     * Check if we are authenticated
     *
     * @return <code>true</code> if we are connected.
     */
    public boolean isAuthenticated()
    {
        return isConnected() && authenticated.get();
    }


    /**
     * Check that a session is valid, ie we can send requests to the
     * server
     *
     * @throws Exception If the session is not valid
     */
    private void checkSession() throws InvalidConnectionException
    {
        if ( ldapSession == null )
        {
            throw new InvalidConnectionException( "Cannot connect on the server, the connection is null" );
        }

        if ( !connected.get() )
        {
            throw new InvalidConnectionException( "Cannot connect on the server, the connection is invalid" );
        }
    }
   
   
    private void addToFutureMap( int messageId, ResponseFuture<? extends Response> future )
    {
        LOG.debug( "Adding <" + messageId + ", " + future.getClass().getName() + ">" );
        futureMap.put( messageId, future );
    }
   
   
    private ResponseFuture<? extends Response> getFromFutureMap( int messageId )
    {
        ResponseFuture<? extends Response> future = futureMap.remove( messageId );
       
        if ( future != null )
        {
            LOG.debug( "Removing <" + messageId + ", " + future.getClass().getName() + ">" );
        }
       
        return future;
    }


    private ResponseFuture<? extends Response> peekFromFutureMap( int messageId )
    {
        ResponseFuture<? extends Response> future = futureMap.get( messageId );
       
        // future can be null if there was a abandon operation on that messageId
        if( future != null )
        {
            LOG.debug( "Getting <" + messageId + ", " + future.getClass().getName() + ">" );
        }
       
        return future;
    }


    /**
     * Return the response stored into the current session.
     *
     * @return The last request response
     */
    public LdapMessageCodec getResponse()
    {
        return ( LdapMessageCodec ) ldapSession.getAttribute( LDAP_RESPONSE );
    }


    /**
     * Inject the client Controls into the message
     */
    private void setControls( Map<String, Control> controls, LdapMessageCodec message )
    {
        // Add the controls
        if ( controls != null )
        {
            for ( Control control : controls.values() )
            {
                ControlImpl ctrl = new ControlImpl( control.getOid() );

                ctrl.setValue( control.getValue() );

                message.addControl( ctrl );
            }
        }
    }


    /**
     * Get the smallest timeout from the client timeout and the connection
     * timeout.
     */
    private long getTimeout( long clientTimeOut )
    {
        if ( clientTimeOut <= 0 )
        {
            return ( timeOut <= 0 ) ? Long.MAX_VALUE : timeOut;
        }
        else if ( timeOut <= 0 )
        {
            return clientTimeOut;
        }
        else
        {
            return timeOut < clientTimeOut ? timeOut : clientTimeOut;
        }
    }


    /**
     * Convert a BindResponseCodec to a BindResponse message
     */
    private BindResponse convert( BindResponseCodec bindResponseCodec )
    {
        BindResponse bindResponse = new BindResponse();

        bindResponse.setMessageId( bindResponseCodec.getMessageId() );
        bindResponse.setServerSaslCreds( bindResponseCodec.getServerSaslCreds() );
        bindResponse.setLdapResult( convert( bindResponseCodec.getLdapResult() ) );

        return bindResponse;
    }


    /**
     * Convert a IntermediateResponseCodec to a IntermediateResponse message based on the ResponseFuture's type
     */
    private void setIResponse( IntermediateResponseCodec intermediateResponseCodec, ResponseFuture responseFuture ) throws Exception
    {
        IntermediateResponse intermediateResponse = null;

        if( responseFuture instanceof SearchFuture )
        {
            intermediateResponse = new SearchIntermediateResponse();
        }
        else if( responseFuture instanceof ExtendedFuture )
        {
            intermediateResponse = new ExtendedIntermediateResponse();
        }
        else
        {
            // currently we only support IR for search and extended operations
            throw new UnsupportedOperationException( "Unknown ResponseFuture type " + responseFuture.getClass().getName() );
        }
       
        intermediateResponse.setResponseName( intermediateResponseCodec.getResponseName() );
        intermediateResponse.setResponseValue( intermediateResponseCodec.getResponseValue() );

        responseFuture.set( intermediateResponse );
    }


    /**
     * Convert a LdapResultCodec to a LdapResult message
     */
    private LdapResult convert( LdapResultCodec ldapResultCodec )
    {
        LdapResult ldapResult = new LdapResult();

        ldapResult.setErrorMessage( ldapResultCodec.getErrorMessage() );
        ldapResult.setMatchedDn( ldapResultCodec.getMatchedDN() );

        // Loop on the referrals
        Referral referral = new Referral();

        if ( ldapResultCodec.getReferrals() != null )
        {
            for ( LdapURL url : ldapResultCodec.getReferrals() )
            {
                referral.addLdapUrls( url );
            }
        }

        ldapResult.setReferral( referral );
        ldapResult.setResultCode( ldapResultCodec.getResultCode() );

        return ldapResult;
    }


    /**
     * Convert a SearchResultEntryCodec to a SearchResultEntry message
     */
    private SearchResultEntry convert( SearchResultEntryCodec searchEntryResultCodec )
    {
        SearchResultEntry searchResultEntry = new SearchResultEntry();

        searchResultEntry.setMessageId( searchEntryResultCodec.getMessageId() );
        searchResultEntry.setEntry( searchEntryResultCodec.getEntry() );
        addControls( searchEntryResultCodec, searchResultEntry );

        return searchResultEntry;
    }


    /**
     * Convert a SearchResultDoneCodec to a SearchResultDone message
     */
    private SearchResultDone convert( SearchResultDoneCodec searchResultDoneCodec )
    {
        SearchResultDone searchResultDone = new SearchResultDone();

        searchResultDone.setMessageId( searchResultDoneCodec.getMessageId() );
        searchResultDone.setLdapResult( convert( searchResultDoneCodec.getLdapResult() ) );
        addControls( searchResultDoneCodec, searchResultDone );

        return searchResultDone;
    }


    /**
     * Convert a SearchResultReferenceCodec to a SearchResultReference message
     */
    private SearchResultReference convert( SearchResultReferenceCodec searchEntryReferenceCodec )
    {
        SearchResultReference searchResultReference = new SearchResultReference();

        searchResultReference.setMessageId( searchEntryReferenceCodec.getMessageId() );

        // Loop on the referrals
        Referral referral = new Referral();

        if ( searchEntryReferenceCodec.getSearchResultReferences() != null )
        {
            for ( LdapURL url : searchEntryReferenceCodec.getSearchResultReferences() )
            {
                referral.addLdapUrls( url );
            }
        }

        searchResultReference.setReferral( referral );
        addControls( searchEntryReferenceCodec, searchResultReference );

        return searchResultReference;
    }


    //------------------------- The constructors --------------------------//
    /**
     * Create a new instance of a LdapConnection on localhost,
     * port 389.
     */
    public LdapConnection()
    {
        config.setUseSsl( false );
        config.setLdapPort( config.getDefaultLdapPort() );
        config.setLdapHost( config.getDefaultLdapHost() );
        messageId = new AtomicInteger( 0 );
    }


    /**
     *
     * Creates a new instance of LdapConnection with the given connection configuration.
     *
     * @param config the configuration of the LdapConnection
     */
    public LdapConnection( LdapConnectionConfig config )
    {
        this.config = config;
        messageId = new AtomicInteger( 0 );
       
    }


    /**
     * Create a new instance of a LdapConnection on localhost,
     * port 389 if the SSL flag is off, or 636 otherwise.
     *
     * @param useSsl A flag to tell if it's a SSL connection or not.
     */
    public LdapConnection( boolean useSsl )
    {
        config.setUseSsl( useSsl );
        config.setLdapPort( useSsl ? config.getDefaultLdapsPort() : config.getDefaultLdapPort() );
        config.setLdapHost( config.getDefaultLdapHost() );
        messageId = new AtomicInteger( 0 );
    }


    /**
     * Create a new instance of a LdapConnection on a given
     * server, using the default port (389).
     *
     * @param server The server we want to be connected to
     */
    public LdapConnection( String server )
    {
        config.setUseSsl( false );
        config.setLdapPort( config.getDefaultLdapPort() );
        config.setLdapHost( server );
        messageId = new AtomicInteger( 0 );
    }


    /**
     * Create a new instance of a LdapConnection on a given
     * server, using the default port (389) if the SSL flag
     * is off, or 636 otherwise.
     *
     * @param server The server we want to be connected to
     * @param useSsl A flag to tell if it's a SSL connection or not.
     */
    public LdapConnection( String server, boolean useSsl )
    {
        config.setUseSsl( useSsl );
        config.setLdapPort( useSsl ? config.getDefaultLdapsPort() : config.getDefaultLdapPort() );
        config.setLdapHost( server );
        messageId = new AtomicInteger( 0 );
    }


    /**
     * Create a new instance of a LdapConnection on a
     * given server and a given port. We don't use ssl.
     *
     * @param server The server we want to be connected to
     * @param port The port the server is listening to
     */
    public LdapConnection( String server, int port )
    {
        this( server, port, false );
    }


    /**
     * Create a new instance of a LdapConnection on a given
     * server, and a give port. We set the SSL flag accordingly
     * to the last parameter.
     *
     * @param server The server we want to be connected to
     * @param port The port the server is listening to
     * @param useSsl A flag to tell if it's a SSL connection or not.
     */
    public LdapConnection( String server, int port, boolean useSsl )
    {
        config.setUseSsl( useSsl );
        config.setLdapPort( port );
        config.setLdapHost( server );
        messageId = new AtomicInteger();
    }


    //-------------------------- The methods ---------------------------//
    /**
     * Connect to the remote LDAP server.
     *
     * @return <code>true</code> if the connection is established, false otherwise
     * @throws LdapException if some error has occured
     */
    public boolean connect() throws LdapException, IOException
    {
        if ( ( ldapSession != null ) && connected.get() )
        {
            // No need to connect if we already have a connected session
            return true;
        }

        // Create the connector if needed
        if ( connector == null )
        {
            connector = new NioSocketConnector();
            localConnector = true;

            // Add the codec to the chain
            connector.getFilterChain().addLast( "ldapCodec", ldapProtocolFilter );

            // If we use SSL, we have to add the SslFilter to the chain
            if ( config.isUseSsl() )
            {
                try
                {
                    SSLContext sslContext = SSLContext.getInstance( config.getSslProtocol() );
                    sslContext.init( config.getKeyManagers(), config.getTrustManagers(), config.getSecureRandom() );

                    SslFilter sslFilter = new SslFilter( sslContext );
                    sslFilter.setUseClientMode( true );
                    connector.getFilterChain().addFirst( "sslFilter", sslFilter );
                }
                catch ( Exception e )
                {
                    String msg = "Failed to initialize the SSL context";
                    LOG.error( msg, e );
                    throw new LdapException( msg, e );
                }
            }

            // Add an executor so that this connection can be used
            // for handling more than one request (mainly because
            // we may have to handle some abandon request)
            /*connector.getFilterChain().addLast( "executor",
                new ExecutorFilter( new OrderedThreadPoolExecutor( 10 ), IoEventType.MESSAGE_RECEIVED ) );*/

            // Inject the protocolHandler
            connector.setHandler( this );
        }

        // Build the connection address
        SocketAddress address = new InetSocketAddress( config.getLdapHost(), config.getLdapPort() );

        // And create the connection future
        ConnectFuture connectionFuture = connector.connect( address );

        // Wait until it's established
        connectionFuture.awaitUninterruptibly();

        if ( !connectionFuture.isConnected() )
        {
            // disposing connector if not connected
            try
            {
                close();
            }
            catch ( IOException ioe )
            {
                // Nothing to do
            }

            return false;
        }

        // Get back the session
        ldapSession = connectionFuture.getSession();
        connected.set( true );

        // And inject the current Ldap container into the session
        IAsn1Container ldapMessageContainer = new LdapMessageContainer();

        // Store the container into the session
        ldapSession.setAttribute( "LDAP-Container", ldapMessageContainer );
       
        // Initialize the MessageId
        messageId.set( 0 );

        // And return
        return true;
    }


    /**
     * Disconnect from the remote LDAP server
     *
     * @return <code>true</code> if the connection is closed, false otherwise
     * @throws IOException if some I/O error occurs
     */
    public boolean close() throws IOException
    {
        // Close the session
        if ( ( ldapSession != null ) && connected.get() )
        {
            ldapSession.close( true );
            connected.set( false );
        }

        // And close the connector if it has been created locally
        if ( localConnector )
        {
            // Release the connector
            connector.dispose();
            connector = null;
        }
       
        // Reset the messageId
        messageId.set( 0 );

        return true;
    }


    //------------------------ The LDAP operations ------------------------//
    // Add operations                                                      //
    //---------------------------------------------------------------------//
    /**
     * Add an entry to the server. This is a blocking add : the user has
     * to wait for the response until the AddResponse is returned.
     *
     * @param entry The entry to add
     * @result the add operation's response
     */
    public AddResponse add( Entry entry ) throws LdapException
    {
        if ( entry == null )
        {
            String msg = "Cannot add empty entry";
            LOG.debug( msg );
            throw new NullPointerException( msg );
        }

        return add( new AddRequest( entry ) );
    }


    /**
     * Add an entry to the server asynchronously. This is a non blocking add :
     * the user has to get for the response from the returned Future.
     *
     * @param entry The entry to add
     * @result the add operation's Future
     */
    public AddFuture addAsync( Entry entry ) throws LdapException
    {
        if ( entry == null )
        {
            String msg = "Cannot add null entry";
            LOG.debug( msg );
            throw new NullPointerException( msg );
        }

        return addAsync( new AddRequest( entry ) );
    }


    /**
     * Add an entry present in the AddRequest to the server.
     *
     * @param addRequest the request object containing an entry and controls(if any)
     * @return the add operation's response
     * @throws LdapException
     */
    public AddResponse add( AddRequest addRequest ) throws LdapException
    {
        AddFuture addFuture = addAsync( addRequest );

        // Get the result from the future
        try
        {
            // Read the response, waiting for it if not available immediately
            long timeout = getTimeout( addRequest.getTimeout() );

            // Get the response, blocking
            AddResponse addResponse = ( AddResponse ) addFuture.get( timeout, TimeUnit.MILLISECONDS );
           
            if ( addResponse == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "Add failed : timeout occured" );
                throw new LdapException( TIME_OUT_ERROR );
            }
           
            if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
            {
                // Everything is fine, return the response
                LOG.debug( "Add successful : {}", addResponse );
            }
            else
            {
                // We have had an error
                LOG.debug( "Add failed : {}", addResponse );
            }

            return addResponse;
        }
        catch ( TimeoutException te )
        {
            // Send an abandon request
            if ( !addFuture.isCancelled() )
            {
                abandon( addRequest.getMessageId() );
            }

            // We didn't received anything : this is an error
            LOG.error( "Add failed : timeout occured" );
            throw new LdapException( TIME_OUT_ERROR );
        }
        catch ( Exception ie )
        {
            // Catch all other exceptions
            LOG.error( NO_RESPONSE_ERROR, ie );
            LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
            ldapException.initCause( ie );

            // Send an abandon request
            if ( !addFuture.isCancelled() )
            {
                abandon( addRequest.getMessageId() );
            }

            throw ldapException;
        }
    }


    /**
     * Add an entry present in the AddRequest to the server.
     *
     * @param addRequest the request object containing an entry and controls(if any)
     * @return the add operation's response
     * @throws LdapException
     */
    public AddFuture addAsync( AddRequest addRequest ) throws LdapException
    {
        checkSession();

        AddRequestCodec addReqCodec = new AddRequestCodec();

        int newId = messageId.incrementAndGet();
       
        addRequest.setMessageId( newId );
        addReqCodec.setMessageId( newId );

        addReqCodec.setEntry( addRequest.getEntry() );
        addReqCodec.setEntryDn( addRequest.getEntry().getDn() );
        setControls( addRequest.getControls(), addReqCodec );

        AddFuture addFuture = new AddFuture( this, newId );
        addToFutureMap( newId, addFuture );

        // Send the request to the server
        WriteFuture writeFuture = ldapSession.write( addReqCodec );

        // Wait for the message to be sent to the server
        if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
        {
            // We didn't received anything : this is an error
            LOG.error( "Add failed : timeout occured" );

            throw new LdapException( TIME_OUT_ERROR );
        }
       
        // Ok, done return the future
        return addFuture;
    }


    /**
     * converts the AddResponseCodec to AddResponse.
     */
    private AddResponse convert( AddResponseCodec addRespCodec )
    {
        AddResponse addResponse = new AddResponse();

        addResponse.setMessageId( addRespCodec.getMessageId() );
        addResponse.setLdapResult( convert( addRespCodec.getLdapResult() ) );

        return addResponse;
    }


    //------------------------ The LDAP operations ------------------------//

    /**
     * Abandons a request submitted to the server for performing a particular operation
     *
     * The abandonRequest is always non-blocking, because no response is expected
     *
     * @param messageId the ID of the request message sent to the server
     */
    public void abandon( int messageId )
    {
        AbandonRequest abandonRequest = new AbandonRequest();
        abandonRequest.setAbandonedMessageId( messageId );

        abandonInternal( abandonRequest );
    }


    /**
     * An abandon request essentially with the request message ID of the operation to be cancelled
     * and/or potentially some controls and timeout (the controls and timeout are not mandatory).
     *
     * The abandonRequest is always non-blocking, because no response is expected
     * 
     * @param abandonRequest the abandon operation's request
     */
    public void abandon( AbandonRequest abandonRequest )
    {
        abandonInternal( abandonRequest );
    }


    /**
     * Internal AbandonRequest handling
     */
    private void abandonInternal( AbandonRequest abandonRequest )
    {
        // Create the inner abandonRequest
        AbandonRequestCodec request = new AbandonRequestCodec();

        // Todo : The Abandon messageID is always 0
        int newId = messageId.incrementAndGet();
        abandonRequest.setMessageId( newId );
        request.setMessageId( newId );

        // Inject the data into the request
        request.setAbandonedMessageId( abandonRequest.getAbandonedMessageId() );

        // Inject the controls
        setControls( abandonRequest.getControls(), request );

        LOG.debug( "-----------------------------------------------------------------" );
        LOG.debug( "Sending request \n{}", request );

        // Send the request to the server
        ldapSession.write( request );

        // remove the associated listener if any
        int abandonId = abandonRequest.getAbandonedMessageId();

        ResponseFuture rf = getFromFutureMap( abandonId );

        // if the listener is not null, this is a async operation and no need to
        // send cancel signal on future, sending so will leave a dangling poision object in the corresponding queue
        // this is a sync operation send cancel signal to the corresponding ResponseFuture
        if ( rf != null )
        {
            LOG.debug( "sending cancel signal to future" );
            rf.cancel( true );
        }
        else
        {
            // this shouldn't happen
            LOG
                .error(
                    "There is no future asscoiated with operation message ID {}, perhaps the operation would have been completed",
                    abandonId );
        }
    }


    /**
     * Anonymous Bind on a server.
     *
     * @return The BindResponse LdapResponse
     */
    public BindResponse bind() throws LdapException, IOException
    {
        LOG.debug( "Anonymous Bind request" );

        // Create the BindRequest
        BindRequest bindRequest = new BindRequest();
        bindRequest.setName( StringTools.EMPTY );
        bindRequest.setCredentials( StringTools.EMPTY_BYTES );

        return bind( bindRequest );
    }


    /**
     * Anonymous asynchronous Bind on a server.
     *
     * @return The BindFuture
     */
    public BindFuture bindAsync() throws LdapException, IOException
    {
        LOG.debug( "Anonymous Bind request" );

        // Create the BindRequest
        BindRequest bindRequest = new BindRequest();
        bindRequest.setName( StringTools.EMPTY );
        bindRequest.setCredentials( StringTools.EMPTY_BYTES );

        return bindAsync( bindRequest );
    }


    /**
     * Simple Bind on a server.
     *
     * @param name The name we use to authenticate the user. It must be a
     * valid DN
     * @param credentials The password. It can't be null
     * @return The BindResponse LdapResponse
     */
    public BindResponse bind( String name, String credentials ) throws LdapException, IOException
    {
        LOG.debug( "Bind request : {}", name );

        // Create the BindRequest
        BindRequest bindRequest = new BindRequest();
        bindRequest.setName( name );
        bindRequest.setCredentials( StringTools.getBytesUtf8( credentials ) );

        return bind( bindRequest );
    }


    /**
     * Simple asynchronous Bind on a server.
     *
     * @param name The name we use to authenticate the user. It must be a
     * valid DN
     * @param credentials The password. It can't be null
     * @return The BindResponse LdapResponse
     */
    public BindFuture bindAsync( String name, String credentials ) throws LdapException, IOException
    {
        LOG.debug( "Bind request : {}", name );
       
        // Create the BindRequest
        BindRequest bindRequest = new BindRequest();
        bindRequest.setName( name );
        bindRequest.setCredentials( StringTools.getBytesUtf8( credentials ) );

        return bindAsync( bindRequest );
    }


    /**
     * Simple Bind on a server.
     *
     * @param name The name we use to authenticate the user. It must be a
     * valid DN
     * @param credentials The password. It can't be null
     * @return The BindResponse LdapResponse
     */
    public BindResponse bind( DN name, String credentials ) throws LdapException, IOException
    {
        LOG.debug( "Bind request : {}", name );

        // Create the BindRequest
        BindRequest bindRequest = new BindRequest();
        bindRequest.setCredentials( StringTools.getBytesUtf8( credentials ) );

        if ( name == null )
        {
            bindRequest.setName( StringTools.EMPTY );
        }
        else
        {
            bindRequest.setName( name.getName() );
        }
       
        return bind( bindRequest );
    }


    /**
     * Simple asynchronous Bind on a server.
     *
     * @param name The name we use to authenticate the user. It must be a
     * valid DN
     * @param credentials The password. It can't be null
     * @return The BindResponse LdapResponse
     */
    public BindFuture bindAsync( DN name, String credentials ) throws LdapException, IOException
    {
        LOG.debug( "Bind request : {}", name );

        // Create the BindRequest
        BindRequest bindRequest = new BindRequest();
        bindRequest.setCredentials( StringTools.getBytesUtf8( credentials ) );

        if ( name == null )
        {
            bindRequest.setName( StringTools.EMPTY );
        }
        else
        {
            bindRequest.setName( name.getName() );
        }
       
        return bindAsync( bindRequest );
    }


    /**
     * Bind to the server using a BindRequest object.
     *
     * @param bindRequest The BindRequest POJO containing all the needed
     * parameters
     * @return A LdapResponse containing the result
     */
    public BindResponse bind( BindRequest bindRequest ) throws LdapException, IOException
    {
        BindFuture bindFuture = bindAsync( bindRequest );

        // Get the result from the future
        try
        {
            // Read the response, waiting for it if not available immediately
            long timeout = getTimeout( bindRequest.getTimeout() );

            // Get the response, blocking
            BindResponse bindResponse = ( BindResponse ) bindFuture.get( timeout, TimeUnit.MILLISECONDS );
           
            if ( bindResponse == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "Bind failed : timeout occured" );
                throw new LdapException( TIME_OUT_ERROR );
            }
           
            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
            {
                authenticated.set( true );

                // Everything is fine, return the response
                LOG.debug( "Bind successful : {}", bindResponse );
            }
            else
            {
                // We have had an error
                LOG.debug( "Bind failed : {}", bindResponse );
            }

            return bindResponse;
        }
        catch ( TimeoutException te )
        {
            // Send an abandon request
            if ( !bindFuture.isCancelled() )
            {
                abandon( bindRequest.getMessageId() );
            }

            // We didn't received anything : this is an error
            LOG.error( "Bind failed : timeout occured" );
            throw new LdapException( TIME_OUT_ERROR );
        }
        catch ( Exception ie )
        {
            // Catch all other exceptions
            LOG.error( NO_RESPONSE_ERROR, ie );
            LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
            ldapException.initCause( ie );

            // Send an abandon request
            if ( !bindFuture.isCancelled() )
            {
                abandon( bindRequest.getMessageId() );
            }

            throw ldapException;
        }
    }
   
   
    /**
     * Create a SearchRequestCodec ready to be sent.
     */
    private SearchRequestCodec createSearchMessage( SearchRequest searchRequest ) throws LdapException
    {
        // Create a new codec SearchRequest object
        SearchRequestCodec request = new SearchRequestCodec();

        // Creates the messageID and stores it into the
        // initial message and the transmitted message.
        int newId = messageId.incrementAndGet();
        searchRequest.setMessageId( newId );
        request.setMessageId( newId );

        // Set the name
        try
        {
            DN dn = new DN( searchRequest.getBaseDn() );
            request.setBaseObject( dn );
        }
        catch ( InvalidNameException ine )
        {
            String msg = "The given dn '" + searchRequest.getBaseDn() + "' is not valid";
            LOG.error( msg );
            LdapException ldapException = new LdapException( msg );
            ldapException.initCause( ine );

            throw ldapException;
        }

        // Set the scope
        request.setScope( searchRequest.getScope() );

        // Set the typesOnly flag
        request.setDerefAliases( searchRequest.getDerefAliases().getValue() );

        // Set the timeLimit
        request.setTimeLimit( searchRequest.getTimeLimit() );

        // Set the sizeLimit
        request.setSizeLimit( searchRequest.getSizeLimit() );

        // Set the typesOnly flag
        request.setTypesOnly( searchRequest.getTypesOnly() );

        // Set the filter
        Filter filter = null;

        try
        {
            ExprNode filterNode = FilterParser.parse( searchRequest.getFilter() );

            filter = LdapTransformer.transformFilter( filterNode );
        }
        catch ( ParseException pe )
        {
            String msg = "The given filter '" + searchRequest.getFilter() + "' is not valid";
            LOG.error( msg );
            LdapException ldapException = new LdapException( msg );
            ldapException.initCause( pe );

            throw ldapException;
        }

        request.setFilter( filter );

        // Set the attributes
        Set<String> attributes = searchRequest.getAttributes();

        if ( attributes != null )
        {
            for ( String attribute : attributes )
            {
                request.addAttribute( attribute );
            }
        }

        // Add the controls
        setControls( searchRequest.getControls(), request );

        return request;
    }


    /**
     * Create a BindRequest ready to be sent.
     */
    private BindRequestCodec createBindMessage( BindRequest bindRequest ) throws LdapException
    {
        // Create a new codec BindRequest object
        BindRequestCodec bindRequestCodec = new BindRequestCodec();

        // clear the mappings if any (in case of a second call to bind() without calling unBind())
        //clearMaps();
       
        // Set the new messageId
        int newId = messageId.incrementAndGet();
        bindRequest.setMessageId( newId );
        bindRequestCodec.setMessageId( newId );

        // Set the version
        bindRequestCodec.setVersion( LdapConnectionConfig.LDAP_V3 );

        // Set the name
        try
        {
            DN dn = new DN( bindRequest.getName() );
            bindRequestCodec.setName( dn );
        }
        catch ( InvalidNameException ine )
        {
            String msg = "The given dn '" + bindRequest.getName() + "' is not valid";
            LOG.error( msg );
            LdapException ldapException = new LdapException( msg );
            ldapException.initCause( ine );

            throw ldapException;
        }

        // Set the credentials
        LdapAuthentication authentication = null;

        if ( bindRequest.isSimple() )
        {
            // Simple bind
            authentication = new SimpleAuthentication();
            ( ( SimpleAuthentication ) authentication ).setSimple( bindRequest.getCredentials() );
        }
        else
        {
            // SASL bind
            authentication = new SaslCredentials();
            ( ( SaslCredentials ) authentication ).setCredentials( bindRequest.getCredentials() );
            ( ( SaslCredentials ) authentication ).setMechanism( bindRequest.getSaslMechanism() );
        }

        // The authentication
        bindRequestCodec.setAuthentication( authentication );

        // Add the controls
        setControls( bindRequest.getControls(), bindRequestCodec );

        return bindRequestCodec;
    }


    /**
     * Do an asynchronous bind, based on a BindRequest.
     *
     * @param bindRequest The BindRequest to send
     * @return BindFuture A future
     */
    public BindFuture bindAsync( BindRequest bindRequest ) throws LdapException, IOException
    {
        // First switch to anonymous state
        authenticated.set( false );
       
        // try to connect, if we aren't already connected.
        connect();

        // If the session has not been establish, or is closed, we get out immediately
        checkSession();

        // Create the new message and update the messageId
        LdapMessageCodec bindMessage = createBindMessage( bindRequest );

        int newId = bindMessage.getMessageId();

        LOG.debug( "-----------------------------------------------------------------" );
        LOG.debug( "Sending request \n{}", bindMessage );

        // Create a future for this Bind operation
        BindFuture bindFuture = new BindFuture( this, newId );

        addToFutureMap( newId, bindFuture );

        // Send the request to the server
        WriteFuture writeFuture = ldapSession.write( bindMessage );

        // Wait for the message to be sent to the server
        if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
        {
            // We didn't received anything : this is an error
            LOG.error( "Bind failed : timeout occured" );

            throw new LdapException( TIME_OUT_ERROR );
        }
       
        // Ok, done return the future
        return bindFuture;
    }


    /**
     * Do a search, on the base object, using the given filter. The
     * SearchRequest parameters default to :
     * Scope : ONE
     * DerefAlias : ALWAYS
     * SizeLimit : none
     * TimeLimit : none
     * TypesOnly : false
     * Attributes : all the user's attributes.
     * This method is blocking.
     *
     * @param baseDn The base for the search. It must be a valid
     * DN, and can't be emtpy
     * @param filterString The filter to use for this search. It can't be empty
     * @param scope The sarch scope : OBJECT, ONELEVEL or SUBTREE
     * @return A cursor on the result.
     */
    public Cursor<SearchResponse> search( String baseDn, String filter, SearchScope scope, String... attributes )
        throws LdapException
    {
        // Create a new SearchRequest object
        SearchRequest searchRequest = new SearchRequest();

        searchRequest.setBaseDn( baseDn );
        searchRequest.setFilter( filter );
        searchRequest.setScope( scope );
        searchRequest.addAttributes( attributes );
        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );

        // Process the request in blocking mode
        return search( searchRequest );
    }


    /**
     * Do an asynchronous search, on the base object, using the given filter. The
     * SearchRequest parameters default to :
     * Scope : ONE
     * DerefAlias : ALWAYS
     * SizeLimit : none
     * TimeLimit : none
     * TypesOnly : false
     * Attributes : all the user's attributes.
     * This method is blocking.
     *
     * @param baseDn The base for the search. It must be a valid
     * DN, and can't be emtpy
     * @param filterString The filter to use for this search. It can't be empty
     * @param scope The sarch scope : OBJECT, ONELEVEL or SUBTREE
     * @return A cursor on the result.
     */
    public SearchFuture searchAsync( String baseDn, String filter, SearchScope scope, String... attributes )
        throws LdapException
    {
        // Create a new SearchRequest object
        SearchRequest searchRequest = new SearchRequest();

        searchRequest.setBaseDn( baseDn );
        searchRequest.setFilter( filter );
        searchRequest.setScope( scope );
        searchRequest.addAttributes( attributes );
        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );

        // Process the request in blocking mode
        return searchAsync( searchRequest );
    }


    /**
     * Do a search, on the base object, using the given filter. The
     * SearchRequest parameters default to :
     * Scope : ONE
     * DerefAlias : ALWAYS
     * SizeLimit : none
     * TimeLimit : none
     * TypesOnly : false
     * Attributes : all the user's attributes.
     * This method is blocking.
     *
     * @param searchRequest The search request to send to the server
     * @return A Future
     */
    public SearchFuture searchAsync( SearchRequest searchRequest ) throws LdapException
    {
        // If the session has not been establish, or is closed, we get out immediately
        checkSession();

        // Create the server request
        SearchRequestCodec request = createSearchMessage( searchRequest );

        LOG.debug( "-----------------------------------------------------------------" );
        LOG.debug( "Sending request \n{}", request );

        SearchFuture searchFuture = new SearchFuture( this, request.getMessageId() );
        addToFutureMap( request.getMessageId(), searchFuture );

        // Send the request to the server
        WriteFuture writeFuture = ldapSession.write( request );

        // Wait for the message to be sent to the server
        if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
        {
            // We didn't received anything : this is an error
            LOG.error( "Search failed : timeout occured" );

            throw new LdapException( TIME_OUT_ERROR );
        }
       
        // Ok, done return the future
        return searchFuture;

    }


    /**
     * Performs search in a synchronous mode.
     * 
     * @param searchRequest The search configuration
     * @return A {@link Cursor} containing Entries and Referencs
     * @throws LdapException @TODO
     */
    public Cursor<SearchResponse> search( SearchRequest searchRequest ) throws LdapException
    {
        SearchFuture searchFuture = searchAsync( searchRequest );

        long timeout = getTimeout( searchRequest.getTimeout() );
       
        return new SearchCursor( searchFuture, timeout, TimeUnit.MILLISECONDS );
    }


    //------------------------ The LDAP operations ------------------------//
    // Unbind operations                                                   //
    //---------------------------------------------------------------------//
    /**
     * UnBind from a server. this is a request which expect no response.
     */
    public void unBind() throws Exception
    {
        // If the session has not been establish, or is closed, we get out immediately
        checkSession();

        // Create the UnbindRequest
        UnBindRequestCodec unbindRequest = new UnBindRequestCodec();

        // Creates the messageID and stores it into the
        // initial message and the transmitted message.
        int newId = messageId.incrementAndGet();
        unbindRequest.setMessageId( newId );

        LOG.debug( "-----------------------------------------------------------------" );
        LOG.debug( "Sending Unbind request \n{}", unbindRequest );

        // Send the request to the server
        WriteFuture unbindFuture = ldapSession.write( unbindRequest );

        //LOG.debug( "waiting for unbindFuture" );
        //unbindFuture.awaitUninterruptibly();
        //LOG.debug( "unbindFuture done" );
       
        authenticated.set( false );

        // clear the mappings
        clearMaps();
       
        //  We now have to close the session
        if ( ( ldapSession != null ) && connected.get() )
        {
            CloseFuture closeFuture = ldapSession.close( true );

            LOG.debug( "waiting for closeFuture" );
            closeFuture.awaitUninterruptibly();
            LOG.debug( "closeFuture done" );
            connected.set( false );
        }
       
        // Last, not least, reset the MessageId value
        messageId.set(0);

        // And get out
        LOG.debug( "Unbind successful" );
    }


    /**
     * Set the connector to use.
     *
     * @param connector The connector to use
     */
    public void setConnector( IoConnector connector )
    {
        this.connector = connector;
    }


    /**
     * Set the timeOut for the responses. We wont wait longer than this
     * value.
     *
     * @param timeOut The timeout, in milliseconds
     */
    public void setTimeOut( long timeOut )
    {
        this.timeOut = timeOut;
    }


    /**
     * Handle the incoming LDAP messages. This is where we feed the cursor for search
     * requests, or call the listener.
     */
    public void messageReceived( IoSession session, Object message ) throws Exception
    {
        // Feed the response and store it into the session
        LdapMessageCodec response = ( LdapMessageCodec ) message;
       
        LOG.debug( "-------> {} Message received <-------", response );

        // this check is necessary to prevent adding an abandoned operation's
        // result(s) to corresponding queue
        ResponseFuture<? extends Response> responseFuture = peekFromFutureMap( response.getMessageId() );

        if ( responseFuture == null )
        {
            LOG.info( "There is no future associated with the messageId {}, ignoring the message", response
                .getMessageId() );
            return;
        }

        int messageId = response.getMessageId();

        switch ( response.getMessageType() )
        {
            case ADD_RESPONSE:
                // Transform the response
                AddResponseCodec addRespCodec = (AddResponseCodec)response;
                addRespCodec.addControl( response.getCurrentControl() );
                addRespCodec.setMessageId( messageId );

                AddResponse addResponse = convert( addRespCodec );
               
                AddFuture addFuture = (AddFuture)responseFuture;

                if ( addFuture == null )
                {
                    LOG.error( "AddFuture is null" );
                    throw new LdapException( "AddFuture is null"  );
                }
               
                // remove the listener from the listener map
                if ( LOG.isDebugEnabled() )
                {
                    if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
                    {
                        // Everything is fine, return the response
                        LOG.debug( "Add successful : {}", addResponse );
                    }
                    else
                    {
                        // We have had an error
                        LOG.debug( "Add failed : {}", addResponse );
                    }
                }

                // Store the response into the future
                addFuture.set( addResponse );
               
                // Remove the future from the map
                removeFromFutureMaps( messageId );
               
                break;

            case BIND_RESPONSE:
                // Transform the response
                BindResponseCodec bindResponseCodec = (BindResponseCodec)response;
                bindResponseCodec.setMessageId( messageId );
                bindResponseCodec.addControl( response.getCurrentControl() );
                BindResponse bindResponse = convert( bindResponseCodec );

                BindFuture bindFuture = (BindFuture)responseFuture;

                if ( bindFuture == null )
                {
                    LOG.error( "BindFuture is null" );
                    throw new LdapException( "BindFuture is null"  );
                }
               
                // remove the listener from the listener map
                if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
                {
                    authenticated.set( true );

                    // Everything is fine, return the response
                    LOG.debug( "Bind successful : {}", bindResponse );
                }
                else
                {
                    // We have had an error
                    LOG.debug( "Bind failed : {}", bindResponse );
                }

                // Store the response into the future
                bindFuture.set( bindResponse );

                // Remove the future from the map
                removeFromFutureMaps( messageId );

                break;

            case COMPARE_RESPONSE:
                // Transform the response
                CompareResponseCodec compResCodec = (CompareResponseCodec)response;
                compResCodec.setMessageId( messageId );
                compResCodec.addControl( response.getCurrentControl() );

                CompareResponse compareResponse = convert( compResCodec );
               
                CompareFuture compareFuture = (CompareFuture)responseFuture;

                if ( compareFuture == null )
                {
                    LOG.error( "CompareFuture is null" );
                    throw new LdapException( "CompareFuture is null"  );
                }
               
                // remove the listener from the listener map
                if ( LOG.isDebugEnabled() )
                {
                    if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
                    {
                        // Everything is fine, return the response
                        LOG.debug( "Compare successful : {}", compareResponse );
                    }
                    else
                    {
                        // We have had an error
                        LOG.debug( "Compare failed : {}", compareResponse );
                    }
                }

                // Store the response into the future
                compareFuture.set( compareResponse );
               
                // Remove the future from the map
                removeFromFutureMaps( messageId );
               
                break;

            case DEL_RESPONSE:
                // Transform the response
                DelResponseCodec delRespCodec = (DelResponseCodec)response;
                delRespCodec.addControl( response.getCurrentControl() );
                delRespCodec.setMessageId( messageId );

                DeleteResponse deleteResponse = convert( delRespCodec );
               
                DeleteFuture deleteFuture = (DeleteFuture)responseFuture;

                if ( deleteFuture == null )
                {
                    LOG.error( "DeleteFuture is null" );
                    throw new LdapException( "DeleteFuture is null"  );
                }
               
                if ( LOG.isDebugEnabled() )
                {
                    if ( deleteResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
                    {
                        // Everything is fine, return the response
                        LOG.debug( "Delete successful : {}", deleteResponse );
                    }
                    else
                    {
                        // We have had an error
                        LOG.debug( "Delete failed : {}", deleteResponse );
                    }
                }

                // Store the response into the future
                deleteFuture.set( deleteResponse );
               
                // Remove the future from the map
                removeFromFutureMaps( messageId );
               
                break;

            case EXTENDED_RESPONSE:
                // Transform the response
                ExtendedResponseCodec extResCodec = (ExtendedResponseCodec)response;
                extResCodec.setMessageId( messageId );
                extResCodec.addControl( response.getCurrentControl() );

                ExtendedResponse extendedResponse = convert( extResCodec );

                ExtendedFuture extendedFuture = (ExtendedFuture)responseFuture;

                if ( extendedFuture == null )
                {
                    LOG.error( "ExtendedFuture is null" );
                    throw new LdapException( "extendedFuture is null"  );
                }

                // remove the listener from the listener map
                if ( LOG.isDebugEnabled() )
                {
                    if ( extendedResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
                    {
                        // Everything is fine, return the response
                        LOG.debug( "Extended successful : {}", extendedResponse );
                    }
                    else
                    {
                        // We have had an error
                        LOG.debug( "Extended failed : {}", extendedResponse );
                    }
                }

                // Store the response into the future
                extendedFuture.set( extendedResponse );

                // Remove the future from the map
                removeFromFutureMaps( messageId );

                break;

            case INTERMEDIATE_RESPONSE:
                IntermediateResponseCodec intermediateResponseCodec = (IntermediateResponseCodec)response;
                intermediateResponseCodec.setMessageId( messageId );
                intermediateResponseCodec.addControl( response.getCurrentControl() );

                setIResponse( intermediateResponseCodec, responseFuture );
               
                break;

            case MODIFY_RESPONSE:
                // Transform the response
                ModifyResponseCodec modRespCodec = (ModifyResponseCodec)response;
                modRespCodec.setMessageId( messageId );
                modRespCodec.addControl( response.getCurrentControl() );

                ModifyResponse modifyResp = convert( modRespCodec );
               
                ModifyFuture modifyFuture = (ModifyFuture)responseFuture;

                if ( modifyFuture == null )
                {
                    LOG.error( "ModifyFuture is null" );
                    throw new LdapException( "ModifyFuture is null"  );
                }
               
                if ( LOG.isDebugEnabled() )
                {
                    if ( modifyResp.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
                    {
                        // Everything is fine, return the response
                        LOG.debug( "ModifyFuture successful : {}", modifyResp );
                    }
                    else
                    {
                        // We have had an error
                        LOG.debug( "ModifyFuture failed : {}", modifyResp );
                    }
                }

                // Store the response into the future
                modifyFuture.set( modifyResp );
               
                // Remove the future from the map
                removeFromFutureMaps( messageId );
               
                break;

            case MODIFYDN_RESPONSE:
                // Transform the response
                ModifyDNResponseCodec modDnRespCodec = (ModifyDNResponseCodec)response;
                modDnRespCodec.setMessageId( messageId );
                modDnRespCodec.addControl( response.getCurrentControl() );

                ModifyDnResponse modifyDnResp = convert( modDnRespCodec );
               
                ModifyDnFuture modifyDnFuture = (ModifyDnFuture)responseFuture;

                if ( modifyDnFuture == null )
                {
                    LOG.error( "ModifyDNFuture is null" );
                    throw new LdapException( "ModifyDNFuture is null"  );
                }
               
                if ( LOG.isDebugEnabled() )
                {
                    if ( modifyDnResp.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
                    {
                        // Everything is fine, return the response
                        LOG.debug( "ModifyDN successful : {}", modifyDnResp );
                    }
                    else
                    {
                        // We have had an error
                        LOG.debug( "ModifyDN failed : {}", modifyDnResp );
                    }
                }

                // Store the response into the future
                modifyDnFuture.set( modifyDnResp );
               
                // Remove the future from the map
                removeFromFutureMaps( messageId );
               
                break;
               
            case SEARCH_RESULT_DONE:
                // Store the response into the responseQueue
                SearchResultDoneCodec searchResultDoneCodec = (SearchResultDoneCodec)response;
                searchResultDoneCodec.setMessageId( messageId );
                searchResultDoneCodec.addControl( response.getCurrentControl() );
                SearchResultDone searchResultDone = convert( searchResultDoneCodec );

                SearchFuture searchFuture = (SearchFuture)responseFuture;
               
                if ( searchFuture == null )
                {
                    LOG.error( "SearchFuture is null" );
                    throw new LdapException( "SearchFuture is null"  );
                }
               
                if ( LOG.isDebugEnabled() )
                {
                    if ( searchResultDone.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
                    {
                        // Everything is fine, return the response
                        LOG.debug( "Search successful : {}", searchResultDone );
                    }
                    else
                    {
                        // We have had an error
                        LOG.debug( "Search failed : {}", searchResultDone );
                    }
                }

                // Store the response into the future
                searchFuture.set( searchResultDone );
               
                // Remove the future from the map
                removeFromFutureMaps( messageId );

                break;

            case SEARCH_RESULT_ENTRY:
                // Store the response into the responseQueue
                SearchResultEntryCodec searchResultEntryCodec = (SearchResultEntryCodec)response;
                searchResultEntryCodec.setMessageId( messageId );
                searchResultEntryCodec.addControl( response.getCurrentControl() );

                SearchResultEntry srchEntry = convert( searchResultEntryCodec );

                searchFuture = (SearchFuture)responseFuture;
               
                if ( searchFuture == null )
                {
                    LOG.error( "SearchFuture is null" );
                    throw new LdapException( "SearchFuture is null"  );
                }
               
                if ( LOG.isDebugEnabled() )
                {
                    LOG.debug( "Search entry found : {}", srchEntry );
                }

                // Store the response into the future
                searchFuture.set( srchEntry );
               
                break;

            case SEARCH_RESULT_REFERENCE:
                // Store the response into the responseQueue
                SearchResultReferenceCodec searchResultReferenceCodec = (SearchResultReferenceCodec)response;
                searchResultReferenceCodec.setMessageId( messageId );
                searchResultReferenceCodec.addControl( response.getCurrentControl() );

                SearchResultReference searchResultReference = convert( searchResultReferenceCodec );

                searchFuture = (SearchFuture)responseFuture;
               
                if ( searchFuture == null )
                {
                    LOG.error( "SearchFuture is null" );
                    throw new LdapException( "SearchFuture is null"  );
                }
               
                if ( LOG.isDebugEnabled() )
                {
                    LOG.debug( "Search reference found : {}", searchResultReference );
                }

                // Store the response into the future
                searchFuture.set( searchResultReference );
               
                break;

            default:
                LOG.error( "~~~~~~~~~~~~~~~~~~~~~ Unknown message type {} ~~~~~~~~~~~~~~~~~~~~~", response
                    .getMessageTypeName() );
        }
    }


    /**
     *
     * modifies all the attributes present in the entry by applying the same operation.
     *
     * @param entry the entry whise attributes to be modified
     * @param modOp the operation to be applied on all the attributes of the above entry
     * @return the modify operation's response
     * @throws LdapException in case of modify operation failure or timeout happens
     */
    public ModifyResponse modify( Entry entry, ModificationOperation modOp ) throws LdapException
    {
        if ( entry == null )
        {
            LOG.debug( "received a null entry for modification" );
            throw new NullPointerException( "Entry to be modified cannot be null" );
        }

        ModifyRequest modReq = new ModifyRequest( entry.getDn() );

        Iterator<EntryAttribute> itr = entry.iterator();
        while ( itr.hasNext() )
        {
            modReq.addModification( itr.next(), modOp );
        }

        return modify( modReq );
    }


    /**
     * Performs an modify operation based on the modifications present in
     * the ModifyRequest.
     *
     * @param modRequest the request for modify operation
     * @return the modify operation's r"esponse
     * @throws LdapException in case of modify operation failure or timeout happens
     */
    public ModifyResponse modify( ModifyRequest modRequest ) throws LdapException
    {
        ModifyFuture modifyFuture = modifyAsync( modRequest );
       
        // Get the result from the future
        try
        {
            // Read the response, waiting for it if not available immediately
            long timeout = getTimeout( modRequest.getTimeout() );

            // Get the response, blocking
            ModifyResponse modifyResponse = ( ModifyResponse ) modifyFuture.get( timeout, TimeUnit.MILLISECONDS );
           
            if ( modifyResponse == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "Modify failed : timeout occured" );
                throw new LdapException( TIME_OUT_ERROR );
            }
           
            if ( modifyResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
            {
                // Everything is fine, return the response
                LOG.debug( "Modify successful : {}", modifyResponse );
            }
            else
            {
                // We have had an error
                LOG.debug( "Modify failed : {}", modifyResponse );
            }

            return modifyResponse;
        }
        catch ( TimeoutException te )
        {
            // Send an abandon request
            if ( !modifyFuture.isCancelled() )
            {
                abandon( modRequest.getMessageId() );
            }

            // We didn't received anything : this is an error
            LOG.error( "Modify failed : timeout occured" );
            throw new LdapException( TIME_OUT_ERROR );
        }
        catch ( Exception ie )
        {
            // Catch all other exceptions
            LOG.error( NO_RESPONSE_ERROR, ie );
            LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
            ldapException.initCause( ie );

            // Send an abandon request
            if ( !modifyFuture.isCancelled() )
            {
                abandon( modRequest.getMessageId() );
            }

            throw ldapException;
        }
    }


    /**
     * Performs an asynchronous modify operation based on the modifications present in
     * the ModifyRequest.
     *
     * @param modRequest the request for modify operation
     * @return the modify operation's future
     * @throws LdapException in case of modify operation failure or timeout happens
     */
    public ModifyFuture modifyAsync( ModifyRequest modRequest ) throws LdapException
    {
        checkSession();

        ModifyRequestCodec modReqCodec = new ModifyRequestCodec();

        int newId = messageId.incrementAndGet();
        modRequest.setMessageId( newId );
        modReqCodec.setMessageId( newId );

        modReqCodec.setModifications( modRequest.getMods() );
        modReqCodec.setObject( modRequest.getDn() );

        setControls( modRequest.getControls(), modReqCodec );
       
        ModifyFuture modifyFuture = new ModifyFuture( this, newId );
        addToFutureMap( newId, modifyFuture );

        // Send the request to the server
        WriteFuture writeFuture = ldapSession.write( modReqCodec );

        // Wait for the message to be sent to the server
        if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
        {
            // We didn't received anything : this is an error
            LOG.error( "Modify failed : timeout occured" );

            throw new LdapException( TIME_OUT_ERROR );
        }
       
        // Ok, done return the future
        return modifyFuture;
    }


    /**
     * converts the ModifyResponseCodec to ModifyResponse.
     */
    private ModifyResponse convert( ModifyResponseCodec modRespCodec )
    {
        ModifyResponse modResponse = new ModifyResponse();

        modResponse.setMessageId( modRespCodec.getMessageId() );
        modResponse.setLdapResult( convert( modRespCodec.getLdapResult() ) );

        return modResponse;
    }


    /**
     * renames the given entryDn with new Rdn and deletes the old RDN.
     * @see #rename(String, String, boolean)
     */
    public ModifyDnResponse rename( String entryDn, String newRdn ) throws LdapException
    {
        return rename( entryDn, newRdn, true );
    }


    /**
     * renames the given entryDn with new RDN and deletes the old RDN.
     * @see #rename(DN, RDN, boolean)
     */
    public ModifyDnResponse rename( DN entryDn, RDN newRdn ) throws LdapException
    {
        return rename( entryDn, newRdn, true );
    }


    /**
     * @see #rename(DN, RDN, boolean)
     */
    public ModifyDnResponse rename( String entryDn, String newRdn, boolean deleteOldRdn ) throws LdapException
    {
        try
        {
            return rename( new DN( entryDn ), new RDN( newRdn ), deleteOldRdn );
        }
        catch ( InvalidNameException e )
        {
            LOG.error( e.getMessage(), e );
            throw new LdapException( e.getMessage(), e );
        }
    }


    /**
     *
     * renames the given entryDn with new RDN and deletes the old Rdn if
     * deleteOldRdn is set to true.
     *
     * @param entryDn the target DN
     * @param newRdn new Rdn for the target DN
     * @param deleteOldRdn flag to indicate whether to delete the old Rdn
     * @return modifyDn operations response
     * @throws LdapException
     */
    public ModifyDnResponse rename( DN entryDn, RDN newRdn, boolean deleteOldRdn ) throws LdapException
    {
        ModifyDnRequest modDnRequest = new ModifyDnRequest();
        modDnRequest.setEntryDn( entryDn );
        modDnRequest.setNewRdn( newRdn );
        modDnRequest.setDeleteOldRdn( deleteOldRdn );

        return modifyDn( modDnRequest );
    }


    /**
     * @see #move(DN, DN)
     */
    public ModifyDnResponse move( String entryDn, String newSuperiorDn ) throws LdapException
    {
        try
        {
            return move( new DN( entryDn ), new DN( newSuperiorDn ) );
        }
        catch ( InvalidNameException e )
        {
            LOG.error( e.getMessage(), e );
            throw new LdapException( e.getMessage(), e );
        }
    }


    /**
     * moves the given entry DN under the new superior DN
     *
     * @param entryDn the DN of the target entry
     * @param newSuperiorDn DN of the new parent/superior
     * @return modifyDn operations response
     * @throws LdapException
     */
    public ModifyDnResponse move( DN entryDn, DN newSuperiorDn ) throws LdapException
    {
        ModifyDnRequest modDnRequest = new ModifyDnRequest();
        modDnRequest.setEntryDn( entryDn );
        modDnRequest.setNewSuperior( newSuperiorDn );

        //TODO not setting the below value is resulting in error
        modDnRequest.setNewRdn( entryDn.getRdn() );

        return modifyDn( modDnRequest );
    }


    /**
     *
     * performs the modifyDn operation based on the given ModifyDnRequest.
     *
     * @param modDnRequest the request
     * @return modifyDn operations response, null if non-null listener is provided
     * @throws LdapException
     */
    public ModifyDnResponse modifyDn( ModifyDnRequest modDnRequest ) throws LdapException
    {
        ModifyDnFuture modifyDnFuture = modifyDnAsync( modDnRequest );
       
        // Get the result from the future
        try
        {
            // Read the response, waiting for it if not available immediately
            long timeout = getTimeout( modDnRequest.getTimeout() );

            // Get the response, blocking
            ModifyDnResponse modifyDnResponse = ( ModifyDnResponse ) modifyDnFuture.get( timeout, TimeUnit.MILLISECONDS );
           
            if ( modifyDnResponse == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "ModifyDN failed : timeout occured" );
                throw new LdapException( TIME_OUT_ERROR );
            }
           
            if ( modifyDnResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
            {
                // Everything is fine, return the response
                LOG.debug( "ModifyDN successful : {}", modifyDnResponse );
            }
            else
            {
                // We have had an error
                LOG.debug( "Modify failed : {}", modifyDnResponse );
            }

            return modifyDnResponse;
        }
        catch ( TimeoutException te )
        {
            // Send an abandon request
            if ( !modifyDnFuture.isCancelled() )
            {
                abandon( modDnRequest.getMessageId() );
            }

            // We didn't received anything : this is an error
            LOG.error( "Modify failed : timeout occured" );
            throw new LdapException( TIME_OUT_ERROR );
        }
        catch ( Exception ie )
        {
            // Catch all other exceptions
            LOG.error( NO_RESPONSE_ERROR, ie );
            LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
            ldapException.initCause( ie );

            // Send an abandon request
            if ( !modifyDnFuture.isCancelled() )
            {
                abandon( modDnRequest.getMessageId() );
            }

            throw ldapException;
        }
    }


    /**
     *
     * performs the modifyDn operation based on the given ModifyDnRequest.
     *
     * @param modDnRequest the request
     * @param listener callback listener which will be called after the operation is completed
     * @return modifyDn operations response, null if non-null listener is provided
     * @throws LdapException
     */
    public ModifyDnFuture modifyDnAsync( ModifyDnRequest modDnRequest ) throws LdapException
    {
        checkSession();

        ModifyDNRequestCodec modDnCodec = new ModifyDNRequestCodec();

        int newId = messageId.incrementAndGet();
        modDnRequest.setMessageId( newId );
        modDnCodec.setMessageId( newId );

        modDnCodec.setEntry( modDnRequest.getEntryDn() );
        modDnCodec.setNewRDN( modDnRequest.getNewRdn() );
        modDnCodec.setDeleteOldRDN( modDnRequest.isDeleteOldRdn() );
        modDnCodec.setNewSuperior( modDnRequest.getNewSuperior() );

        setControls( modDnRequest.getControls(), modDnCodec );

        ModifyDnFuture modifyDnFuture = new ModifyDnFuture( this, newId );
        addToFutureMap( newId, modifyDnFuture );

        // Send the request to the server
        WriteFuture writeFuture = ldapSession.write( modDnCodec );

        // Wait for the message to be sent to the server
        if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
        {
            // We didn't received anything : this is an error
            LOG.error( "Modify failed : timeout occured" );

            throw new LdapException( TIME_OUT_ERROR );
        }
       
        // Ok, done return the future
        return modifyDnFuture;
    }


    /**
     * converts the ModifyDnResponseCodec to ModifyResponse.
     */
    private ModifyDnResponse convert( ModifyDNResponseCodec modDnRespCodec )
    {
        ModifyDnResponse modDnResponse = new ModifyDnResponse();

        modDnResponse.setMessageId( modDnRespCodec.getMessageId() );
        modDnResponse.setLdapResult( convert( modDnRespCodec.getLdapResult() ) );

        return modDnResponse;
    }


    /**
     * deletes the entry with the given DN
     * 
     * @param dn the target entry's DN as a String
     * @throws LdapException If the DN is not valid or if the deletion failed
     */
    public DeleteResponse delete( String dn ) throws LdapException
    {
        try
        {
            DeleteRequest deleteRequest = new DeleteRequest( new DN( dn ) );

            return delete( deleteRequest );
        }
        catch ( InvalidNameException e )
        {
            LOG.error( e.getMessage(), e );
            throw new LdapException( e.getMessage(), e );
        }
    }


    /**
     * deletes the entry with the given DN
     * 
     * @param dn the target entry's DN
     * @throws LdapException If the DN is not valid or if the deletion failed
     */
    public DeleteResponse delete( DN dn ) throws LdapException
    {
        DeleteRequest deleteRequest = new DeleteRequest( dn );

        return delete( deleteRequest );
    }


    /**
     * deletes the entry with the given DN, and all its children
     * 
     * @param dn the target entry's DN
     * @return operation's response
     * @throws LdapException If the DN is not valid or if the deletion failed
     */
    public DeleteResponse deleteTree( DN dn ) throws LdapException
    {
        String treeDeleteOid = "1.2.840.113556.1.4.805";

        if ( isControlSupported( treeDeleteOid ) )
        {
            DeleteRequest delRequest = new DeleteRequest( dn );
            delRequest.add( new ControlImpl( treeDeleteOid ) );
            return delete( delRequest );
        }
        else
        {
            String msg = "The subtreeDelete control (1.2.840.113556.1.4.805) is not supported by the server\n" +
                " The deletion has been aborted";
            LOG.error( msg );
            throw new LdapException( msg );
        }
    }


    /**
     * deletes the entry with the given DN, and all its children
     * 
     * @param dn the target entry's DN as a String
     * @return operation's response
     * @throws LdapException If the DN is not valid or if the deletion failed
     */
    public DeleteResponse deleteTree( String dn ) throws LdapException
    {
        try
        {
            String treeDeleteOid = "1.2.840.113556.1.4.805";
            DN newDn = new DN( dn );

            if ( isControlSupported( treeDeleteOid ) )
            {
                DeleteRequest delRequest = new DeleteRequest( newDn );
                delRequest.add( new ControlImpl( treeDeleteOid ) );
                return delete( delRequest );
            }
            else
            {
                String msg = "The subtreeDelete control (1.2.840.113556.1.4.805) is not supported by the server\n" +
                    " The deletion has been aborted";
                LOG.error( msg );
                throw new LdapException( msg );
            }
        }
        catch ( InvalidNameException e )
        {
            LOG.error( e.getMessage(), e );
            throw new LdapException( e.getMessage(), e );
        }
    }


    /**
     * removes all child entries present under the given DN and finally the DN itself
     *
     * Working:
     *          This is a recursive function which maintains a Map<DN,Cursor>.
     *          The way the cascade delete works is by checking for children for a
     *          given DN(i.e opening a search cursor) and if the cursor is empty
     *          then delete the DN else for each entry's DN present in cursor call
     *          deleteChildren() with the DN and the reference to the map.
     *         
     *          The reason for opening a search cursor is based on an assumption
     *          that an entry *might* contain children, consider the below DIT fragment
     *         
     *          parent
     *          /     \
     *        child1   child2
     *                 /     \
     *               grand21  grand22
     *              
     *           The below method works better in the case where the tree depth is >1
     *         
     *   In the case of passing a non-null DeleteListener, the return value will always be null, cause the
     *   operation is treated as asynchronous and response result will be sent using the listener callback
     *  
     *  //FIXME provide another method for optimizing delete operation for a tree with depth <=1
     *         
     * @param dn the DN which will be removed after removing its children
     * @param map a map to hold the Cursor related to a DN
     * @param listener  the delete operation response listener
     * @throws LdapException If the DN is not valid or if the deletion failed
     */
    private DeleteResponse deleteRecursive( DN dn, Map<DN, Cursor<SearchResponse>> cursorMap,
        DeleteListener listener ) throws LdapException
    {
        LOG.debug( "searching for {}", dn.getName() );
        DeleteResponse delResponse = null;
        Cursor<SearchResponse> cursor = null;

        try
        {
            if ( cursorMap == null )
            {
                cursorMap = new HashMap<DN, Cursor<SearchResponse>>();
            }

            cursor = cursorMap.get( dn );

            if ( cursor == null )
            {
                cursor = search( dn.getName(), "(objectClass=*)", SearchScope.ONELEVEL, ( String[] ) null );
                LOG.debug( "putting cursor for {}", dn.getName() );
                cursorMap.put( dn, cursor );
            }

            if ( !cursor.next() ) // if this is a leaf entry's DN
            {
                LOG.debug( "deleting {}", dn.getName() );
                cursorMap.remove( dn );
                cursor.close();
                delResponse = delete( new DeleteRequest( dn ) );
            }
            else
            {
                do
                {
                    SearchResponse searchResp = cursor.get();

                    if ( searchResp instanceof SearchResultEntry )
                    {
                        SearchResultEntry searchResult = ( SearchResultEntry ) searchResp;
                        deleteRecursive( searchResult.getEntry().getDn(), cursorMap, listener );
                    }
                }
                while ( cursor.next() );

                cursorMap.remove( dn );
                cursor.close();
                LOG.debug( "deleting {}", dn.getName() );
                delResponse = delete( new DeleteRequest( dn ) );
            }
        }
        catch ( Exception e )
        {
            String msg = "Failed to delete child entries under the DN " + dn.getName();
            LOG.error( msg, e );
            throw new LdapException( msg, e );
        }

        return delResponse;
    }


    /**
     * Performs a delete operation based on the delete request object.
     * 
     * @param deleteRequest the delete operation's request
     * @return delete operation's response, null if a non-null listener value is provided
     * @throws LdapException If the DN is not valid or if the deletion failed
     */
    public DeleteResponse delete( DeleteRequest deleteRequest ) throws LdapException
    {
        DeleteFuture deleteFuture = deleteAsync( deleteRequest );

        // Get the result from the future
        try
        {
            // Read the response, waiting for it if not available immediately
            long timeout = getTimeout( deleteRequest.getTimeout() );

            // Get the response, blocking
            DeleteResponse delResponse = ( DeleteResponse ) deleteFuture.get( timeout, TimeUnit.MILLISECONDS );
           
            if ( delResponse == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "Delete failed : timeout occured" );
                throw new LdapException( TIME_OUT_ERROR );
            }
           
            if ( delResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
            {
                // Everything is fine, return the response
                LOG.debug( "Delete successful : {}", delResponse );
            }
            else
            {
                // We have had an error
                LOG.debug( "Delete failed : {}", delResponse );
            }

            return delResponse;
        }
        catch ( TimeoutException te )
        {
            // Send an abandon request
            if ( !deleteFuture.isCancelled() )
            {
                abandon( deleteRequest.getMessageId() );
            }

            // We didn't received anything : this is an error
            LOG.error( "Del failed : timeout occured" );
            throw new LdapException( TIME_OUT_ERROR );
        }
        catch ( Exception ie )
        {
            // Catch all other exceptions
            LOG.error( NO_RESPONSE_ERROR, ie );
            LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
            ldapException.initCause( ie );

            // Send an abandon request
            if ( !deleteFuture.isCancelled() )
            {
                abandon( deleteRequest.getMessageId() );
            }

            throw ldapException;
        }
    }
   
   
    /**
     * Performs an asynchronous delete operation based on the delete request object.
     * 
     * @param delRequest the delete operation's request
     * @return delete operation's response, null if a non-null listener value is provided
     * @throws LdapException If the DN is not valid or if the deletion failed
     */
    public DeleteFuture deleteAsync( DeleteRequest delRequest ) throws LdapException
    {
        checkSession();

        DelRequestCodec delReqCodec = new DelRequestCodec();

        int newId = messageId.incrementAndGet();
       
        delRequest.setMessageId( newId );
        delReqCodec.setMessageId( newId );

        delReqCodec.setEntry( delRequest.getTargetDn() );
        setControls( delRequest.getControls(), delReqCodec );

        DeleteFuture deleteFuture = new DeleteFuture( this, newId );
        addToFutureMap( newId, deleteFuture );

        // Send the request to the server
        WriteFuture writeFuture = ldapSession.write( delReqCodec );

        // Wait for the message to be sent to the server
        if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
        {
            // We didn't received anything : this is an error
            LOG.error( "Delete failed : timeout occured" );

            throw new LdapException( TIME_OUT_ERROR );
        }
       
        // Ok, done return the future
        return deleteFuture;
    }


    /**
     * Compares a whether a given attribute's value matches that of the
     * existing value of the attribute present in the entry with the given DN
     *
     * @param dn the target entry's String DN
     * @param attributeName the attribute's name
     * @param value a String value with which the target entry's attribute value to be compared with
     * @return compare operation's response
     * @throws LdapException
     */
    public CompareResponse compare( String dn, String attributeName, String value ) throws LdapException
    {
        try
        {
            CompareRequest compareRequest = new CompareRequest();
            compareRequest.setEntryDn( new DN( dn ) );
            compareRequest.setAttrName( attributeName );
            compareRequest.setValue( value );

            return compare( compareRequest );
        }
        catch ( Exception e )
        {
            LOG.error( COMPARE_FAILED, e );
            throw new LdapException( COMPARE_FAILED, e );
        }
    }


    /**
     * Compares a whether a given attribute's value matches that of the
     * existing value of the attribute present in the entry with the given DN
     *
     * @param dn the target entry's String DN
     * @param attributeName the attribute's name
     * @param value a byte[] value with which the target entry's attribute value to be compared with
     * @return compare operation's response
     * @throws LdapException
     */
    public CompareResponse compare( String dn, String attributeName, byte[] value ) throws LdapException
    {
        try
        {
            CompareRequest compareRequest = new CompareRequest();
            compareRequest.setEntryDn( new DN( dn ) );
            compareRequest.setAttrName( attributeName );
            compareRequest.setValue( value );

            return compare( compareRequest );
        }
        catch ( Exception e )
        {
            LOG.error( COMPARE_FAILED, e );
            throw new LdapException( COMPARE_FAILED, e );
        }
    }


    /**
     * Compares a whether a given attribute's value matches that of the
     * existing value of the attribute present in the entry with the given DN
     *
     * @param dn the target entry's String DN
     * @param attributeName the attribute's name
     * @param value a Value<?> value with which the target entry's attribute value to be compared with
     * @return compare operation's response
     * @throws LdapException
     */
    public CompareResponse compare( String dn, String attributeName, Value<?> value ) throws LdapException
    {
        try
        {
            CompareRequest compareRequest = new CompareRequest();
            compareRequest.setEntryDn( new DN( dn ) );
            compareRequest.setAttrName( attributeName );
            compareRequest.setValue( value );

            return compare( compareRequest );
        }
        catch ( Exception e )
        {
            LOG.error( COMPARE_FAILED, e );
            throw new LdapException( COMPARE_FAILED, e );
        }
    }


    /**
     * Compares a whether a given attribute's value matches that of the
     * existing value of the attribute present in the entry with the given DN
     *
     * @param dn the target entry's DN
     * @param attributeName the attribute's name
     * @param value a String value with which the target entry's attribute value to be compared with
     * @return compare operation's response
     * @throws LdapException
     */
    public CompareResponse compare( DN dn, String attributeName, String value ) throws LdapException
    {
        CompareRequest compareRequest = new CompareRequest();
        compareRequest.setEntryDn( dn );
        compareRequest.setAttrName( attributeName );
        compareRequest.setValue( value );

        return compare( compareRequest );
    }


    /**
     * Compares a whether a given attribute's value matches that of the
     * existing value of the attribute present in the entry with the given DN
     *
     * @param dn the target entry's DN
     * @param attributeName the attribute's name
     * @param value a byte[] value with which the target entry's attribute value to be compared with
     * @return compare operation's response
     * @throws LdapException
     */
    public CompareResponse compare( DN dn, String attributeName, byte[] value ) throws LdapException
    {
        CompareRequest compareRequest = new CompareRequest();
        compareRequest.setEntryDn( dn );
        compareRequest.setAttrName( attributeName );
        compareRequest.setValue( value );

        return compare( compareRequest );
    }


    /**
     * Compares a whether a given attribute's value matches that of the
     * existing value of the attribute present in the entry with the given DN
     *
     * @param dn the target entry's DN
     * @param attributeName the attribute's name
     * @param value a Value<?> value with which the target entry's attribute value to be compared with
     * @return compare operation's response
     * @throws LdapException
     */
    public CompareResponse compare( DN dn, String attributeName, Value<?> value ) throws LdapException
    {
        CompareRequest compareRequest = new CompareRequest();
        compareRequest.setEntryDn( dn );
        compareRequest.setAttrName( attributeName );
        compareRequest.setValue( value.get() );

        return compare( compareRequest );
    }


    /**
     * compares an entry's attribute's value with that of the given value
     *  
     * @param compareRequest the CompareRequest which contains the target DN, attribute name and value
     * @return compare operation's response
     * @throws LdapException
     */
    public CompareResponse compare( CompareRequest compareRequest ) throws LdapException
    {
        CompareFuture compareFuture = compareAsync( compareRequest );

        // Get the result from the future
        try
        {
            // Read the response, waiting for it if not available immediately
            long timeout = getTimeout( compareRequest.getTimeout() );

            // Get the response, blocking
            CompareResponse compareResponse = ( CompareResponse ) compareFuture.get( timeout, TimeUnit.MILLISECONDS );
           
            if ( compareResponse == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "Compare failed : timeout occured" );
                throw new LdapException( TIME_OUT_ERROR );
            }
           
            if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
            {
                // Everything is fine, return the response
                LOG.debug( "Compare successful : {}", compareResponse );
            }
            else
            {
                // We have had an error
                LOG.debug( "Compare failed : {}", compareResponse );
            }

            return compareResponse;
        }
        catch ( TimeoutException te )
        {
            // Send an abandon request
            if ( !compareFuture.isCancelled() )
            {
                abandon( compareRequest.getMessageId() );
            }

            // We didn't received anything : this is an error
            LOG.error( "Compare failed : timeout occured" );
            throw new LdapException( TIME_OUT_ERROR );
        }
        catch ( Exception ie )
        {
            // Catch all other exceptions
            LOG.error( NO_RESPONSE_ERROR, ie );
            LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
            ldapException.initCause( ie );

            // Send an abandon request
            if ( !compareFuture.isCancelled() )
            {
                abandon( compareRequest.getMessageId() );
            }

            throw ldapException;
        }
    }


    /**
     * Asynchronously compares an entry's attribute's value with that of the given value
     *  
     * @param compareRequest the CompareRequest which contains the target DN, attribute name and value
     * @return compare operation's future
     * @throws LdapException
     */
    public CompareFuture compareAsync( CompareRequest compareRequest ) throws LdapException
    {
        checkSession();

        CompareRequestCodec compareReqCodec = new CompareRequestCodec();

        int newId = messageId.incrementAndGet();
       
        compareRequest.setMessageId( newId );
        compareReqCodec.setMessageId( newId );

        compareReqCodec.setEntry( compareRequest.getEntryDn() );
        compareReqCodec.setAttributeDesc( compareRequest.getAttrName() );
        compareReqCodec.setAssertionValue( compareRequest.getValue() );
        setControls( compareRequest.getControls(), compareReqCodec );

        CompareFuture compareFuture = new CompareFuture( this, newId );
        addToFutureMap( newId, compareFuture );

        // Send the request to the server
        WriteFuture writeFuture = ldapSession.write( compareReqCodec );

        // Wait for the message to be sent to the server
        if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
        {
            // We didn't received anything : this is an error
            LOG.error( "Compare failed : timeout occured" );

            throw new LdapException( TIME_OUT_ERROR );
        }
       
        // Ok, done return the future
        return compareFuture;
    }


    /**
     * converts the CompareResponseCodec to CompareResponse.
     */
    private CompareResponse convert( CompareResponseCodec compareRespCodec )
    {
        CompareResponse compareResponse = new CompareResponse();

        compareResponse.setMessageId( compareRespCodec.getMessageId() );
        compareResponse.setLdapResult( convert( compareRespCodec.getLdapResult() ) );

        return compareResponse;
    }


    /**
     * converts the DeleteResponseCodec to DeleteResponse object.
     */
    private DeleteResponse convert( DelResponseCodec delRespCodec )
    {
        DeleteResponse response = new DeleteResponse();

        response.setMessageId( delRespCodec.getMessageId() );
        response.setLdapResult( convert( delRespCodec.getLdapResult() ) );

        return response;
    }


    /**
     * @see #extended(OID, byte[])
     */
    public ExtendedResponse extended( String oid ) throws LdapException
    {
        return extended( oid, null );
    }


    /**
     * @see #extended(OID, byte[])
     */
    public ExtendedResponse extended( String oid, byte[] value ) throws LdapException
    {
        try
        {
            return extended( new OID( oid ), value );
        }
        catch ( DecoderException e )
        {
            String msg = "Failed to decode the OID " + oid;
            LOG.error( msg );
            throw new LdapException( msg, e );
        }
    }


    /**
     * @see #extended(OID, byte[])
     */
    public ExtendedResponse extended( OID oid ) throws LdapException
    {
        return extended( oid, null );
    }


    /**
     * sends a extended operation request to the server with the given OID and value
     *
     * @param oid the object identifier of the extended operation
     * @param value value to be used by the extended operation, can be a null value
     * @return extended operation's response
     * @throws LdapException
     */
    public ExtendedResponse extended( OID oid, byte[] value ) throws LdapException
    {
        ExtendedRequest extRequest = new ExtendedRequest( oid );
        extRequest.setValue( value );

        return extended( extRequest );
    }


    /**
     * Performs an extended operation based on the Extended request object.
     *
     * @param extendedRequest the extended operation's request
     * @return Extended operation's response
     * @throws LdapException If the DN is not valid or if the extended operation failed
     */
    public ExtendedResponse extended( ExtendedRequest extendedRequest ) throws LdapException
    {
        ExtendedFuture extendedFuture = extendedAsync( extendedRequest );

        // Get the result from the future
        try
        {
            // Read the response, waiting for it if not available immediately
            long timeout = getTimeout( extendedRequest.getTimeout() );

            // Get the response, blocking
            ExtendedResponse extendedResponse = ( ExtendedResponse ) extendedFuture.get( timeout, TimeUnit.MILLISECONDS );

            if ( extendedResponse == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "Extended failed : timeout occured" );
                throw new LdapException( TIME_OUT_ERROR );
            }

            if ( extendedResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
            {
                // Everything is fine, return the response
                LOG.debug( "Extended successful : {}", extendedResponse );
            }
            else
            {
                // We have had an error
                LOG.debug( "Extended failed : {}", extendedResponse );
            }

            return extendedResponse;
        }
        catch ( TimeoutException te )
        {
            // Send an abandon request
            if ( !extendedFuture.isCancelled() )
            {
                abandon( extendedRequest.getMessageId() );
            }

            // We didn't received anything : this is an error
            LOG.error( "Extended failed : timeout occured" );
            throw new LdapException( TIME_OUT_ERROR );
        }
        catch ( Exception ie )
        {
            // Catch all other exceptions
            LOG.error( NO_RESPONSE_ERROR, ie );
            LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
            ldapException.initCause( ie );

            // Send an abandon request
            if ( !extendedFuture.isCancelled() )
            {
                abandon( extendedRequest.getMessageId() );
            }

            throw ldapException;
        }
    }


    /**
     * Asynchronously requests the server to perform an extended operation based on the given request.
     *
     * @param extendedRequest the object containing the details of the extended operation to be performed
     * @return extended operation's Future
     * @throws LdapException
     */
    public ExtendedFuture extendedAsync( ExtendedRequest extendedRequest ) throws LdapException
    {
        checkSession();

        ExtendedRequestCodec extReqCodec = new ExtendedRequestCodec();

        int newId = messageId.incrementAndGet();

        extReqCodec.setMessageId( newId );
        extReqCodec.setMessageId( newId );

        extReqCodec.setRequestName( extendedRequest.getOid() );
        extReqCodec.setRequestValue( extendedRequest.getValue() );
        setControls( extendedRequest.getControls(), extReqCodec );

        ExtendedFuture extendedFuture = new ExtendedFuture( this, newId );
        addToFutureMap( newId, extendedFuture );

        // Send the request to the server
        WriteFuture writeFuture = ldapSession.write( extReqCodec );

        // Wait for the message to be sent to the server
        if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
        {
            // We didn't received anything : this is an error
            LOG.error( "Extended failed : timeout occured" );

            throw new LdapException( TIME_OUT_ERROR );
        }

        // Ok, done return the future
        return extendedFuture;
    }

    /**
     * @see #lookup(String, String...)
     */
    public SearchResponse lookup( String dn ) throws LdapException
    {
        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
    }
   
   
    /**
     * searches for an entry having the given DN
     *
     * @param dn the DN of the entry to be fetched
     * @param attributes the attributes to be returned along with entry
     * @return the Entry with the given DN or null if no entry exists with that DN
     * @throws LdapException in case of any problems while searching for the DN
     */
    public SearchResponse lookup( String dn, String... attributes ) throws LdapException
    {
        SearchResponse resp = null;
       
        try
        {
            Cursor<SearchResponse> cursor = search( dn, "(objectClass=*)", SearchScope.OBJECT, attributes );
            if( cursor.next() )
            {
                resp = cursor.get();
            }
           
            cursor.close();
        }
        catch( Exception e )
        {
            throw new LdapException( e );
        }

        return resp;
    }
   

    /**
     * converts the ExtendedResponseCodec to ExtendedResponse.
     */
    private ExtendedResponse convert( ExtendedResponseCodec extRespCodec )
    {
        ExtendedResponse extResponse = new ExtendedResponse();

        OID oid = null;
        try
        {
            if ( extRespCodec.getResponseName() != null )
            {
                oid = new OID( extRespCodec.getResponseName() );
            }
        }
        catch ( DecoderException e )
        {
            // can happen in case of a PROTOCOL_ERROR result, ignore
            //LOG.error( "invalid response name {}", extRespCodec.getResponseName() );
        }

        extResponse.setOid( oid );
        extResponse.setValue( extRespCodec.getResponse() );
        extResponse.setMessageId( extRespCodec.getMessageId() );
        extResponse.setLdapResult( convert( extRespCodec.getLdapResult() ) );

        return extResponse;
    }


    /**
     * checks if a control with the given OID is supported
     *
     * @param controlOID the OID of the control
     * @return true if the control is supported, false otherwise
     */
    public boolean isControlSupported( String controlOID ) throws LdapException
    {
        return getSupportedControls().contains( controlOID );
    }


    /**
     * get the Controls supported by server.
     *
     * @return a list of control OIDs supported by server
     * @throws LdapException
     */
    public List<String> getSupportedControls() throws LdapException
    {
        if ( supportedControls != null )
        {
            return supportedControls;
        }

        if ( rootDSE == null )
        {
            fetchRootDSE();
        }

        supportedControls = new ArrayList<String>();

        EntryAttribute attr = rootDSE.get( SchemaConstants.SUPPORTED_CONTROL_AT );
       
        for (Value<?> value:attr)
        {
            supportedControls.add( value.getString() );
        }

        return supportedControls;
    }

   
    /**
     * loads the default schema that is bundled in the shared-ldap-schema.jar
     *
     * @throws LdapException in case of problems while loading the schema
     */
    public void loadSchema() throws LdapException
    {
        try
        {
            JarLdifSchemaLoader jarSchemaLoader = new JarLdifSchemaLoader();

            schemaManager = new DefaultSchemaManager( jarSchemaLoader );
            schemaManager.loadAllEnabled();
            if( ! schemaManager.getErrors().isEmpty() )
            {
                String msg = "there are errors while loading the schema";
                LOG.error( msg + " {}", schemaManager.getErrors() );
                throw new LdapException( msg );
            }
        }
        catch( LdapException le )
        {
            throw le;
        }
        catch( Exception e )
        {
            LOG.error( "failed to load the schema", e );
            throw new LdapException( e );
        }
    }

   
    public SchemaManager getSchemaManager()
    {
        return schemaManager;
    }


    /**
     * fetches the rootDSE from the server
     * @throws LdapException
     */
    private void fetchRootDSE() throws LdapException
    {
        Cursor<SearchResponse> cursor = null;
        try
        {
            cursor = search( "", "(objectClass=*)", SearchScope.OBJECT, "*", "+" );
            cursor.next();
            SearchResultEntry searchRes = ( SearchResultEntry ) cursor.get();

            rootDSE = searchRes.getEntry();
        }
        catch ( Exception e )
        {
            String msg = "Failed to fetch the RootDSE";
            LOG.error( msg );
            throw new LdapException( msg, e );
        }
        finally
        {
            if ( cursor != null )
            {
                try
                {
                    cursor.close();
                }
                catch ( Exception e )
                {
                    LOG.error( "Failed to close open cursor", e );
                }
            }
        }
    }


    /**
     * gives the configuration information of the connection
     *
     * @return the configuration of the connection
     */
    public LdapConnectionConfig getConfig()
    {
        return config;
    }


    private void addControls( LdapMessageCodec codec, AbstractMessage message )
    {
        List<Control> ccList = codec.getControls();
        if ( ccList != null )
        {
            for ( Control cc : ccList )
            {
                // FIXME why the cc is coming as null!?
                if ( cc == null )
                {
                    continue;
                }

                Control control = new ControlImpl( cc.getOid() );
                control.setValue( cc.getValue() );
                control.setCritical( cc.isCritical() );

                message.add( control );
            }
        }
    }


    /**
     * removes the Objects associated with the given message ID
     * from future and response queue maps
     *
     * @param msgId id of the message
     */
    private void removeFromFutureMaps( int msgId )
    {
        getFromFutureMap( msgId );
    }
   
   
    /**
     * clears the async listener, responseQueue and future mapppings to the corresponding request IDs
     */
    private void clearMaps()
    {
        futureMap.clear();
    }
   
   
    /**
     * checks if there is a ResponseFuture associated with the given message id
     *
     * @param messageId ID of the request
     * @return true if there is a non-null future exists, false otherwise
     */
    public boolean doesFutureExistFor( Integer messageId )
    {
        return futureMap.get( messageId ) != null;
    }
}
TOP

Related Classes of org.apache.directory.ldap.client.api.LdapConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.