001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.ldap.client.api;
021
022
023import static org.apache.directory.api.ldap.model.message.ResultCodeEnum.processResponse;
024
025import java.io.File;
026import java.io.IOException;
027import java.io.OutputStreamWriter;
028import java.io.Writer;
029import java.net.ConnectException;
030import java.net.InetSocketAddress;
031import java.net.SocketAddress;
032import java.nio.channels.UnresolvedAddressException;
033import java.nio.charset.Charset;
034import java.nio.file.Files;
035import java.nio.file.Paths;
036import java.security.PrivilegedExceptionAction;
037import java.util.ArrayList;
038import java.util.HashMap;
039import java.util.Iterator;
040import java.util.List;
041import java.util.Map;
042import java.util.concurrent.CompletableFuture;
043import java.util.concurrent.ConcurrentHashMap;
044import java.util.concurrent.ExecutionException;
045import java.util.concurrent.TimeUnit;
046import java.util.concurrent.TimeoutException;
047import java.util.concurrent.atomic.AtomicBoolean;
048import java.util.concurrent.locks.ReentrantLock;
049
050import javax.net.ssl.SSLContext;
051import javax.net.ssl.TrustManager;
052import javax.security.auth.Subject;
053import javax.security.auth.login.Configuration;
054import javax.security.auth.login.LoginContext;
055import javax.security.sasl.Sasl;
056import javax.security.sasl.SaslClient;
057
058import org.apache.directory.api.asn1.DecoderException;
059import org.apache.directory.api.asn1.util.Oid;
060import org.apache.directory.api.i18n.I18n;
061import org.apache.directory.api.ldap.codec.api.BinaryAttributeDetector;
062import org.apache.directory.api.ldap.codec.api.DefaultConfigurableBinaryAttributeDetector;
063import org.apache.directory.api.ldap.codec.api.ExtendedOperationFactory;
064import org.apache.directory.api.ldap.codec.api.LdapApiService;
065import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
066import org.apache.directory.api.ldap.codec.api.LdapDecoder;
067import org.apache.directory.api.ldap.codec.api.LdapMessageContainer;
068import org.apache.directory.api.ldap.codec.api.MessageEncoderException;
069import org.apache.directory.api.ldap.codec.api.SchemaBinaryAttributeDetector;
070import org.apache.directory.api.ldap.extras.controls.ad.TreeDelete;
071import org.apache.directory.api.ldap.extras.controls.ad.TreeDeleteImpl;
072import org.apache.directory.api.ldap.extras.extended.startTls.StartTlsRequestImpl;
073import org.apache.directory.api.ldap.model.constants.LdapConstants;
074import org.apache.directory.api.ldap.model.constants.SchemaConstants;
075import org.apache.directory.api.ldap.model.cursor.Cursor;
076import org.apache.directory.api.ldap.model.cursor.CursorException;
077import org.apache.directory.api.ldap.model.cursor.EntryCursor;
078import org.apache.directory.api.ldap.model.cursor.SearchCursor;
079import org.apache.directory.api.ldap.model.entry.Attribute;
080import org.apache.directory.api.ldap.model.entry.DefaultEntry;
081import org.apache.directory.api.ldap.model.entry.Entry;
082import org.apache.directory.api.ldap.model.entry.Modification;
083import org.apache.directory.api.ldap.model.entry.ModificationOperation;
084import org.apache.directory.api.ldap.model.entry.Value;
085import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
086import org.apache.directory.api.ldap.model.exception.LdapException;
087import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
088import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
089import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
090import org.apache.directory.api.ldap.model.exception.LdapOperationException;
091import org.apache.directory.api.ldap.model.exception.LdapOtherException;
092import org.apache.directory.api.ldap.model.exception.LdapTlsHandshakeException;
093import org.apache.directory.api.ldap.model.message.AbandonRequest;
094import org.apache.directory.api.ldap.model.message.AbandonRequestImpl;
095import org.apache.directory.api.ldap.model.message.AddRequest;
096import org.apache.directory.api.ldap.model.message.AddRequestImpl;
097import org.apache.directory.api.ldap.model.message.AddResponse;
098import org.apache.directory.api.ldap.model.message.AliasDerefMode;
099import org.apache.directory.api.ldap.model.message.BindRequest;
100import org.apache.directory.api.ldap.model.message.BindRequestImpl;
101import org.apache.directory.api.ldap.model.message.BindResponse;
102import org.apache.directory.api.ldap.model.message.CompareRequest;
103import org.apache.directory.api.ldap.model.message.CompareRequestImpl;
104import org.apache.directory.api.ldap.model.message.CompareResponse;
105import org.apache.directory.api.ldap.model.message.Control;
106import org.apache.directory.api.ldap.model.message.DeleteRequest;
107import org.apache.directory.api.ldap.model.message.DeleteRequestImpl;
108import org.apache.directory.api.ldap.model.message.DeleteResponse;
109import org.apache.directory.api.ldap.model.message.ExtendedRequest;
110import org.apache.directory.api.ldap.model.message.ExtendedResponse;
111import org.apache.directory.api.ldap.model.message.IntermediateResponse;
112import org.apache.directory.api.ldap.model.message.LdapResult;
113import org.apache.directory.api.ldap.model.message.Message;
114import org.apache.directory.api.ldap.model.message.ModifyDnRequest;
115import org.apache.directory.api.ldap.model.message.ModifyDnRequestImpl;
116import org.apache.directory.api.ldap.model.message.ModifyDnResponse;
117import org.apache.directory.api.ldap.model.message.ModifyRequest;
118import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
119import org.apache.directory.api.ldap.model.message.ModifyResponse;
120import org.apache.directory.api.ldap.model.message.OpaqueExtendedRequest;
121import org.apache.directory.api.ldap.model.message.OpaqueExtendedResponse;
122import org.apache.directory.api.ldap.model.message.Request;
123import org.apache.directory.api.ldap.model.message.Response;
124import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
125import org.apache.directory.api.ldap.model.message.SearchRequest;
126import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
127import org.apache.directory.api.ldap.model.message.SearchResultDone;
128import org.apache.directory.api.ldap.model.message.SearchResultEntry;
129import org.apache.directory.api.ldap.model.message.SearchResultReference;
130import org.apache.directory.api.ldap.model.message.SearchScope;
131import org.apache.directory.api.ldap.model.message.UnbindRequest;
132import org.apache.directory.api.ldap.model.message.UnbindRequestImpl;
133import org.apache.directory.api.ldap.model.message.controls.ManageDsaITImpl;
134import org.apache.directory.api.ldap.model.message.controls.OpaqueControl;
135import org.apache.directory.api.ldap.model.message.extended.AddNoDResponse;
136import org.apache.directory.api.ldap.model.message.extended.BindNoDResponse;
137import org.apache.directory.api.ldap.model.message.extended.CompareNoDResponse;
138import org.apache.directory.api.ldap.model.message.extended.DeleteNoDResponse;
139import org.apache.directory.api.ldap.model.message.extended.ExtendedNoDResponse;
140import org.apache.directory.api.ldap.model.message.extended.ModifyDnNoDResponse;
141import org.apache.directory.api.ldap.model.message.extended.ModifyNoDResponse;
142import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect;
143import org.apache.directory.api.ldap.model.message.extended.SearchNoDResponse;
144import org.apache.directory.api.ldap.model.name.Dn;
145import org.apache.directory.api.ldap.model.name.Rdn;
146import org.apache.directory.api.ldap.model.schema.AttributeType;
147import org.apache.directory.api.ldap.model.schema.ObjectClass;
148import org.apache.directory.api.ldap.model.schema.SchemaManager;
149import org.apache.directory.api.ldap.model.schema.parsers.OpenLdapSchemaParser;
150import org.apache.directory.api.ldap.model.schema.registries.Registries;
151import org.apache.directory.api.ldap.model.schema.registries.SchemaLoader;
152import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
153import org.apache.directory.api.util.Network;
154import org.apache.directory.api.util.StringConstants;
155import org.apache.directory.api.util.Strings;
156import org.apache.directory.ldap.client.api.callback.SaslCallbackHandler;
157import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
158import org.apache.directory.ldap.client.api.exception.LdapConnectionTimeOutException;
159import org.apache.directory.ldap.client.api.future.AddFuture;
160import org.apache.directory.ldap.client.api.future.BindFuture;
161import org.apache.directory.ldap.client.api.future.CompareFuture;
162import org.apache.directory.ldap.client.api.future.DeleteFuture;
163import org.apache.directory.ldap.client.api.future.ExtendedFuture;
164import org.apache.directory.ldap.client.api.future.HandshakeFuture;
165import org.apache.directory.ldap.client.api.future.ModifyDnFuture;
166import org.apache.directory.ldap.client.api.future.ModifyFuture;
167import org.apache.directory.ldap.client.api.future.ResponseFuture;
168import org.apache.directory.ldap.client.api.future.SearchFuture;
169import org.apache.mina.core.filterchain.IoFilter;
170import org.apache.mina.core.future.CloseFuture;
171import org.apache.mina.core.future.ConnectFuture;
172import org.apache.mina.core.future.WriteFuture;
173import org.apache.mina.core.service.IoConnector;
174import org.apache.mina.core.session.IoSession;
175import org.apache.mina.filter.FilterEvent;
176import org.apache.mina.filter.codec.ProtocolCodecFilter;
177import org.apache.mina.filter.codec.ProtocolEncoderException;
178import org.apache.mina.filter.ssl.SslEvent;
179import org.apache.mina.filter.ssl.SslFilter;
180import org.apache.mina.transport.socket.SocketSessionConfig;
181import org.apache.mina.transport.socket.nio.NioSocketConnector;
182import org.slf4j.Logger;
183import org.slf4j.LoggerFactory;
184
185
186/**
187 * This class is the base for every operations sent or received to and
188 * from a LDAP server.
189 *
190 * A connection instance is necessary to send requests to the server. The connection
191 * is valid until either the client closes it, the server closes it or the
192 * client does an unbind.
193 *
194 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
195 */
196public class LdapNetworkConnection extends AbstractLdapConnection implements LdapAsyncConnection
197{
198    /** logger for reporting errors that might not be handled properly upstream */
199    private static final Logger LOG = LoggerFactory.getLogger( LdapNetworkConnection.class );
200
201    /** The timeout used for response we are waiting for */
202    private long timeout = LdapConnectionConfig.DEFAULT_TIMEOUT;
203
204    /** configuration object for the connection */
205    private LdapConnectionConfig config;
206    
207    /** The Socket configuration */
208    private SocketSessionConfig socketSessionConfig;
209
210    /** The connector open with the remote server */
211    private IoConnector connector;
212
213    /** A mutex used to avoid a double close of the connector */
214    private ReentrantLock connectorMutex = new ReentrantLock();
215
216    /**
217     * The created session, created when we open a connection with
218     * the Ldap server.
219     */
220    private IoSession ioSession;
221
222    /** a map to hold the ResponseFutures for all operations */
223    private Map<Integer, ResponseFuture<? extends Response>> futureMap = new ConcurrentHashMap<>();
224
225    /** list of controls supported by the server */
226    private List<String> supportedControls;
227
228    /** The ROOT DSE entry */
229    private Entry rootDse;
230
231    /** A flag indicating that the BindRequest has been issued and successfully authenticated the user */
232    private AtomicBoolean authenticated = new AtomicBoolean( false );
233
234    /** a list of listeners interested in getting notified when the
235     *  connection's session gets closed cause of network issues
236     */
237    private List<ConnectionClosedEventListener> conCloseListeners;
238
239    /** The Ldap codec protocol filter */
240    private IoFilter ldapProtocolFilter = new ProtocolCodecFilter( codec.getProtocolCodecFactory() );
241
242    /** the SslFilter key */
243    private static final String SSL_FILTER_KEY = "sslFilter";
244
245    /** The exception stored in the session if we've got one */
246    private static final String EXCEPTION_KEY = "sessionException";
247
248    /** The krb5 configuration property */
249    private static final String KRB5_CONF = "java.security.krb5.conf";
250    
251    /** A future used to block any action until the handshake is completed */
252    private HandshakeFuture handshakeFuture;
253    
254    /** A future used to wait for a connection to be closed */
255    private CompletableFuture<Integer> connectionCloseFuture = new CompletableFuture<>(); 
256    
257    // ~~~~~~~~~~~~~~~~~ common error messages ~~~~~~~~~~~~~~~~~~~~~~~~~~
258    static final String TIME_OUT_ERROR = I18n.err( I18n.ERR_04170_TIMEOUT_OCCURED );
259
260    static final String NO_RESPONSE_ERROR = I18n.err( I18n.ERR_04169_RESPONSE_QUEUE_EMPTIED );
261    
262   //------------------------- The constructors --------------------------//
263    /**
264     * Create a new instance of a LdapConnection on localhost,
265     * port 389.
266     */
267    public LdapNetworkConnection()
268    {
269        this( null, -1, false );
270    }
271
272
273    /**
274     *
275     * Creates a new instance of LdapConnection with the given connection configuration.
276     *
277     * @param config the configuration of the LdapConnection
278     */
279    public LdapNetworkConnection( LdapConnectionConfig config )
280    {
281        this( config, LdapApiServiceFactory.getSingleton() );
282    }
283
284
285    /**
286     * Creates a new LdapNetworkConnection instance
287     * 
288     * @param config The configuration to use
289     * @param ldapApiService The LDAP API Service to use
290     */
291    public LdapNetworkConnection( LdapConnectionConfig config, LdapApiService ldapApiService )
292    {
293        super( ldapApiService );
294        this.config = config;
295
296        if ( config.getBinaryAttributeDetector() == null )
297        {
298            config.setBinaryAttributeDetector( new DefaultConfigurableBinaryAttributeDetector() );
299        }
300        
301        this.timeout = config.getTimeout();
302    }
303
304
305    /**
306     * Create a new instance of a LdapConnection on localhost,
307     * port 389 if the SSL flag is off, or 636 otherwise.
308     *
309     * @param useSsl A flag to tell if it's a SSL connection or not.
310     */
311    public LdapNetworkConnection( boolean useSsl )
312    {
313        this( null, -1, useSsl );
314    }
315
316
317    /**
318     * Creates a new LdapNetworkConnection instance
319     * 
320     * @param useSsl If we are going to create a secure connection or not
321     * @param ldapApiService The LDAP API Service to use
322     */
323    public LdapNetworkConnection( boolean useSsl, LdapApiService ldapApiService )
324    {
325        this( null, -1, useSsl, ldapApiService );
326    }
327
328
329    /**
330     * Create a new instance of a LdapConnection on a given
331     * server, using the default port (389).
332     *
333     * @param server The server we want to be connected to. If null or empty,
334     * we will default to LocalHost.
335     */
336    public LdapNetworkConnection( String server )
337    {
338        this( server, -1, false );
339    }
340
341
342    /**
343     * Creates a new LdapNetworkConnection instance
344     * 
345     * @param server The server we want to be connected to. If null or empty,
346     * we will default to LocalHost.
347     * @param ldapApiService The LDAP API Service to use
348     */
349    public LdapNetworkConnection( String server, LdapApiService ldapApiService )
350    {
351        this( server, -1, false, ldapApiService );
352    }
353
354
355    /**
356     * Create a new instance of a LdapConnection on a given
357     * server, using the default port (389) if the SSL flag
358     * is off, or 636 otherwise.
359     *
360     * @param server The server we want to be connected to. If null or empty,
361     * we will default to LocalHost.
362     * @param useSsl A flag to tell if it's a SSL connection or not.
363     */
364    public LdapNetworkConnection( String server, boolean useSsl )
365    {
366        this( server, -1, useSsl );
367    }
368
369
370    /**
371     * Creates a new LdapNetworkConnection instance
372     * 
373     * @param server The server we want to be connected to. If null or empty,
374     * we will default to LocalHost.
375     * @param useSsl A flag to tell if it's a SSL connection or not.
376     * @param ldapApiService The LDAP API Service to use
377     */
378    public LdapNetworkConnection( String server, boolean useSsl, LdapApiService ldapApiService )
379    {
380        this( server, -1, useSsl, ldapApiService );
381    }
382
383
384    /**
385     * Create a new instance of a LdapConnection on a
386     * given server and a given port. We don't use ssl.
387     *
388     * @param server The server we want to be connected to
389     * @param port The port the server is listening to
390     */
391    public LdapNetworkConnection( String server, int port )
392    {
393        this( server, port, false );
394    }
395
396
397    /**
398     * Create a new instance of a LdapConnection on a
399     * given server and a given port. We don't use ssl.
400     *
401     * @param server The server we want to be connected to. If null or empty,
402     * we will default to LocalHost.
403     * @param port The port the server is listening on
404     * @param ldapApiService The LDAP API Service to use
405     */
406    public LdapNetworkConnection( String server, int port, LdapApiService ldapApiService )
407    {
408        this( server, port, false, ldapApiService );
409    }
410
411
412    /**
413     * Create a new instance of a LdapConnection on a given
414     * server, and a give port. We set the SSL flag accordingly
415     * to the last parameter.
416     *
417     * @param server The server we want to be connected to. If null or empty,
418     * we will default to LocalHost.
419     * @param port The port the server is listening to
420     * @param useSsl A flag to tell if it's a SSL connection or not.
421     */
422    public LdapNetworkConnection( String server, int port, boolean useSsl )
423    {
424        this( buildConfig( server, port, useSsl ) );
425    }
426    
427    
428    /**
429     * Create a new instance of a LdapConnection on a given
430     * server, and a give port. This SSL connection will use the provided
431     * TrustManagers
432     *
433     * @param server The server we want to be connected to. If null or empty,
434     * we will default to LocalHost.
435     * @param port The port the server is listening to
436     * @param trustManagers The TrustManager to use
437     */
438    public LdapNetworkConnection( String server, int port, TrustManager... trustManagers )
439    {
440        this( buildConfig( server, port, true ) );
441        
442        config.setTrustManagers( trustManagers );
443    }
444
445
446    /**
447     * Create a new instance of a LdapConnection on a
448     * given server and a given port. We don't use ssl.
449     *
450     * @param server The server we want to be connected to. If null or empty,
451     * we will default to LocalHost.
452     * @param port The port the server is listening on
453     * @param useSsl A flag to tell if it's a SSL connection or not.
454     * @param ldapApiService The LDAP API Service to use
455     */
456    public LdapNetworkConnection( String server, int port, boolean useSsl, LdapApiService ldapApiService )
457    {
458        this( buildConfig( server, port, useSsl ), ldapApiService );
459    }
460
461
462    private static LdapConnectionConfig buildConfig( String server, int port, boolean useSsl )
463    {
464        LdapConnectionConfig config = new LdapConnectionConfig();
465        config.setUseSsl( useSsl );
466
467        if ( port != -1 )
468        {
469            config.setLdapPort( port );
470        }
471        else
472        {
473            if ( useSsl )
474            {
475                config.setLdapPort( config.getDefaultLdapsPort() );
476            }
477            else
478            {
479                config.setLdapPort( config.getDefaultLdapPort() );
480            }
481        }
482
483        // Default to localhost if null
484        if ( Strings.isEmpty( server ) )
485        {
486            config.setLdapHost( Network.LOOPBACK_HOSTNAME );
487            
488        }
489        else
490        {
491            config.setLdapHost( server );
492        }
493
494        config.setBinaryAttributeDetector( new DefaultConfigurableBinaryAttributeDetector() );
495
496        return config;
497    }
498
499
500    /**
501     * Create the connector
502     * 
503     * @throws LdapException If the connector can't be created
504     */
505    private void createConnector() throws LdapException
506    {
507        // Use only one thread inside the connector
508        connector = new NioSocketConnector( 1 );
509        
510        if ( socketSessionConfig != null )
511        {
512            ( ( SocketSessionConfig ) connector.getSessionConfig() ).setAll( socketSessionConfig );
513        }
514        else
515        {
516            ( ( SocketSessionConfig ) connector.getSessionConfig() ).setReuseAddress( true );
517        }
518
519        // Add the codec to the chain
520        connector.getFilterChain().addLast( "ldapCodec", ldapProtocolFilter );
521
522        // If we use SSL, we have to add the SslFilter to the chain
523        if ( config.isUseSsl() )
524        {
525            addSslFilter();
526        }
527
528        // Inject the protocolHandler
529        connector.setHandler( this );
530    }
531
532
533    //--------------------------- Helper methods ---------------------------//
534    /**
535     * {@inheritDoc}
536     */
537    @Override
538    public boolean isConnected()
539    {
540        return ( ioSession != null ) && ioSession.isConnected() && !ioSession.isClosing();
541        
542    }
543
544
545    /**
546     * {@inheritDoc}
547     */
548    @Override
549    public boolean isAuthenticated()
550    {
551        return isConnected() && authenticated.get();
552    }
553
554
555    /**
556     * Tells if the connection is using a secured channel
557     * 
558     * @return <tt>true</tt> if the session is using a secured channel
559     */
560    public boolean isSecured()
561    {
562        return isConnected() && ioSession.isSecured();
563    }
564
565    
566    /**
567     * Check that a session is valid, ie we can send requests to the
568     * server
569     *
570     * @throws InvalidConnectionException If the session is not valid
571     */
572    private void checkSession() throws InvalidConnectionException
573    {
574        if ( ioSession == null )
575        {
576            throw new InvalidConnectionException( I18n.err( I18n.ERR_04104_NULL_CONNECTION_CANNOT_CONNECT ) );
577        }
578
579        if ( !isConnected() )
580        {
581            throw new InvalidConnectionException( I18n.err( I18n.ERR_04108_INVALID_CONNECTION ) );
582        }
583    }
584
585
586    private void addToFutureMap( int messageId, ResponseFuture<? extends Response> future )
587    {
588        if ( LOG.isDebugEnabled() )
589        {
590            LOG.debug( I18n.msg( I18n.MSG_04106_ADDING, messageId, future.getClass().getName() ) );
591        }
592        
593        futureMap.put( messageId, future );
594    }
595
596
597    private ResponseFuture<? extends Response> getFromFutureMap( int messageId )
598    {
599        ResponseFuture<? extends Response> future = futureMap.remove( messageId );
600
601        if ( LOG.isDebugEnabled() && ( future != null ) )
602        {
603            LOG.debug( I18n.msg( I18n.MSG_04126_REMOVING, messageId, future.getClass().getName() ) );
604        }
605
606        return future;
607    }
608
609
610    private ResponseFuture<? extends Response> peekFromFutureMap( int messageId )
611    {
612        ResponseFuture<? extends Response> future = futureMap.get( messageId );
613
614        // future can be null if there was a abandon operation on that messageId
615        if ( LOG.isDebugEnabled() && ( future != null ) )
616        {
617            LOG.debug( I18n.msg( I18n.MSG_04119_GETTING, messageId, future.getClass().getName() ) );
618        }
619
620        return future;
621    }
622
623
624    /**
625     * Get the largest timeout from the search time limit and the connection
626     * timeout.
627     * 
628     * @param connectionTimoutInMS Connection timeout
629     * @param searchTimeLimitInSeconds Search timeout
630     * @return The largest timeout
631     */
632    public long getTimeout( long connectionTimoutInMS, int searchTimeLimitInSeconds )
633    {
634        if ( searchTimeLimitInSeconds < 0 )
635        {
636            return connectionTimoutInMS;
637        }
638        else if ( searchTimeLimitInSeconds == 0 )
639        {
640            if ( config.getTimeout() == 0 )
641            {
642                return Long.MAX_VALUE;
643            }
644            else
645            {
646                return config.getTimeout();
647            }
648        }
649        else
650        {
651            long searchTimeLimitInMS = searchTimeLimitInSeconds * 1000L;
652            return Math.max( searchTimeLimitInMS, connectionTimoutInMS );
653        }
654    }
655
656    
657    /**
658     * Process the connect. 
659     * 
660     * @exception LdapException If we weren't able to connect
661     * @return A Future that can be used to check the status of the connection
662     */
663    public ConnectFuture tryConnect() throws LdapException
664    {
665        // Build the connection address
666        SocketAddress address = new InetSocketAddress( config.getLdapHost(), config.getLdapPort() );
667        ConnectFuture connectionFuture = connector.connect( address );
668        boolean result = false;
669
670        // Wait until it's established
671        try
672        {
673            result = connectionFuture.await( timeout );
674        }
675        catch ( InterruptedException e )
676        {
677            connector.dispose();
678            connector = null;
679
680            if ( LOG.isDebugEnabled() )
681            {
682                LOG.debug( I18n.msg( I18n.MSG_04120_INTERRUPTED_WAITING_FOR_CONNECTION, 
683                    config.getLdapHost(),
684                    config.getLdapPort() ), e );
685            }
686            
687            throw new LdapOtherException( e.getMessage(), e );
688        }
689
690        if ( !result )
691        {
692            // It may be an exception, or a timeout
693            Throwable connectionException = connectionFuture.getException();
694
695            connector = null;
696
697            if ( connectionException == null )
698            {
699                // This was a timeout
700                String message = I18n.msg( I18n.MSG_04177_CONNECTION_TIMEOUT, timeout );
701                
702                if ( LOG.isDebugEnabled() )
703                {
704                    LOG.debug( message );
705                }
706                
707                throw new LdapConnectionTimeOutException( message );
708            }
709            else
710            {
711                if ( LOG.isDebugEnabled() )
712                {
713                    if ( ( connectionException instanceof ConnectException )
714                        || ( connectionException instanceof UnresolvedAddressException ) )
715                    {
716                        // No need to wait
717                        // We know that there was a permanent error such as "connection refused".
718                        LOG.debug( I18n.msg( I18n.MSG_04144_CONNECTION_ERROR, connectionFuture.getException().getMessage() ) );
719                    }
720    
721                    LOG.debug( I18n.msg( I18n.MSG_04120_INTERRUPTED_WAITING_FOR_CONNECTION, 
722                        config.getLdapHost(),
723                        config.getLdapPort() ), connectionException );
724                }
725                
726                throw new LdapOtherException( connectionException.getMessage(), connectionException );
727            }
728        }
729        
730        return connectionFuture;
731    }
732    
733    
734    /**
735     * Close the connection and generate the appropriate exception
736     * 
737     * @exception LdapException If we weren't able to close the connection
738     */
739    private void close( ConnectFuture connectionFuture ) throws LdapException
740    {
741        // disposing connector if not connected
742        close();
743
744        Throwable e = connectionFuture.getException();
745
746        if ( e != null )
747        {
748            // Special case for UnresolvedAddressException
749            // (most of the time no message is associated with this exception)
750            if ( ( e instanceof UnresolvedAddressException ) && ( e.getMessage() == null ) )
751            {
752                throw new InvalidConnectionException( I18n.err( I18n.ERR_04121_CANNOT_RESOLVE_HOSTNAME, config.getLdapHost() ), e );
753            }
754
755            // Default case
756            throw new InvalidConnectionException( I18n.err( I18n.ERR_04110_CANNOT_CONNECT_TO_SERVER, e.getMessage() ), e );
757        }
758
759        // We didn't received anything : this is an error
760        if ( LOG.isErrorEnabled() )
761        {
762            LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Connect" ) );
763        }
764
765        throw new LdapException( TIME_OUT_ERROR );
766    }
767    
768    
769    /**
770     * Verify that the connection has been secured, otherwise throw a meaningful exception
771     * 
772     * @exception LdapException If we weren't able to check that the connection is secured
773     */
774    private void checkSecured( ConnectFuture connectionFuture ) throws LdapException
775    {
776        try
777        {
778            boolean isSecured = handshakeFuture.get( timeout, TimeUnit.MILLISECONDS );
779
780            if ( !isSecured )
781            {
782                // check for a specific cause
783                Throwable cause = connectionFuture.getException();
784                
785                if ( cause == null && connectionFuture.getSession() != null )
786                {
787                    cause = ( Throwable ) connectionFuture.getSession().getAttribute( EXCEPTION_KEY );
788                }
789                
790                // Cancel the latch
791                connectionCloseFuture.complete( 0 );
792
793                // if there is no cause assume timeout
794                if ( cause == null )
795                {
796                    throw new LdapException( TIME_OUT_ERROR );
797                }
798
799                throw new LdapTlsHandshakeException( I18n.err( I18n.ERR_04120_TLS_HANDSHAKE_ERROR ), cause );
800            }
801        }
802        catch ( Exception e )
803        {
804            if ( e instanceof LdapException )
805            {
806                throw ( LdapException ) e;
807            }
808
809            String msg = I18n.err( I18n.ERR_04122_SSL_CONTEXT_INIT_FAILURE );
810            LOG.error( msg, e );
811            throw new LdapException( msg, e );
812        }
813    }
814    
815    
816    /**
817     * Set a listener associated to the closeFuture
818     * 
819     * @param connectionFuture A Future for which we want to set a listener
820     */
821    private void setCloseListener( ConnectFuture connectionFuture )
822    {
823        // Get the close future for this session
824        CloseFuture closeFuture = connectionFuture.getSession().getCloseFuture();
825        
826        closeFuture.addListener( future -> 
827        {
828            // Process all the waiting operations and cancel them
829            if ( LOG.isDebugEnabled() )
830            {
831                LOG.debug( I18n.msg( I18n.MSG_04137_NOD_RECEIVED ) );
832            }
833
834            for ( ResponseFuture<?> responseFuture : futureMap.values() )
835            {
836                if ( LOG.isDebugEnabled() )
837                {
838                    LOG.debug( I18n.msg( I18n.MSG_04137_NOD_RECEIVED ) );
839                }
840
841                responseFuture.cancel();
842
843                try
844                {
845                    if ( responseFuture instanceof AddFuture )
846                    {
847                        ( ( AddFuture ) responseFuture ).set( AddNoDResponse.PROTOCOLERROR );
848                    }
849                    else if ( responseFuture instanceof BindFuture )
850                    {
851                        ( ( BindFuture ) responseFuture ).set( BindNoDResponse.PROTOCOLERROR );
852                    }
853                    else if ( responseFuture instanceof CompareFuture )
854                    {
855                        ( ( CompareFuture ) responseFuture ).set( CompareNoDResponse.PROTOCOLERROR );
856                    }
857                    else if ( responseFuture instanceof DeleteFuture )
858                    {
859                        ( ( DeleteFuture ) responseFuture ).set( DeleteNoDResponse.PROTOCOLERROR );
860                    }
861                    else if ( responseFuture instanceof ExtendedFuture )
862                    {
863                        ( ( ExtendedFuture ) responseFuture ).set( ExtendedNoDResponse.PROTOCOLERROR );
864                    }
865                    else if ( responseFuture instanceof ModifyFuture )
866                    {
867                        ( ( ModifyFuture ) responseFuture ).set( ModifyNoDResponse.PROTOCOLERROR );
868                    }
869                    else if ( responseFuture instanceof ModifyDnFuture )
870                    {
871                        ( ( ModifyDnFuture ) responseFuture ).set( ModifyDnNoDResponse.PROTOCOLERROR );
872                    }
873                    else if ( responseFuture instanceof SearchFuture )
874                    {
875                        ( ( SearchFuture ) responseFuture ).set( SearchNoDResponse.PROTOCOLERROR );
876                    }
877                }
878                catch ( InterruptedException e )
879                {
880                    LOG.error( I18n.err( I18n.ERR_04113_ERROR_PROCESSING_NOD, responseFuture ), e );
881                }
882
883                futureMap.remove( messageId.get() );
884            }
885
886            futureMap.clear();
887        } );
888    }
889    
890    
891    /**
892     * Set the BinaryDetector instance in the session
893     */
894    private void setBinaryDetector()
895    {
896        @SuppressWarnings("unchecked")
897        LdapMessageContainer<? extends Message> container =
898            ( LdapMessageContainer<? extends Message> ) ioSession
899                .getAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR );
900
901        if ( container != null )
902        {
903            if ( ( schemaManager != null ) && !( container.getBinaryAttributeDetector() instanceof SchemaBinaryAttributeDetector ) )
904            {
905                container.setBinaryAttributeDetector( new SchemaBinaryAttributeDetector( schemaManager ) );
906            }
907        }
908        else
909        {
910            BinaryAttributeDetector atDetector = new DefaultConfigurableBinaryAttributeDetector();
911
912            if ( schemaManager != null )
913            {
914                atDetector = new SchemaBinaryAttributeDetector( schemaManager );
915            }
916
917            ioSession.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR,
918                new LdapMessageContainer<Message>( codec, atDetector ) );
919        }
920    }
921    
922
923    //-------------------------- The methods ---------------------------//
924    /**
925     * {@inheritDoc}
926     */
927    @Override
928    public boolean connect() throws LdapException
929    {
930        if ( isConnected() )
931        {
932            // No need to connect if we already have a connected session
933            return true;
934        }
935        
936        // Create the connector if needed
937        if ( connector == null )
938        {
939            createConnector();
940        }
941
942        // And create the connection future
943        ConnectFuture connectionFuture = tryConnect();
944
945        // Check if we are good to go
946        if ( !connectionFuture.isConnected() )
947        {
948            // Release the latch
949            connectionCloseFuture.cancel( true );
950            
951            close( connectionFuture );
952        }
953
954        // Check if we are secured if requested
955        if ( config.isUseSsl() )
956        {
957            checkSecured( connectionFuture );
958        }
959
960        // Add a listener to close the session in the session.
961        setCloseListener( connectionFuture );
962
963        // Get back the session
964        ioSession = connectionFuture.getSession();
965
966        // Store the container into the session if we don't have one
967        setBinaryDetector();
968
969        // Initialize the MessageId
970        messageId.set( 0 );
971        
972        connectionCloseFuture = new CompletableFuture<>();
973
974        // And return
975        return true;
976    }
977
978
979    /**
980     * {@inheritDoc}
981     */
982    @Override
983    public void close()
984    {
985        // Close the session
986        if ( isConnected() )
987        {
988            ioSession.closeNow();
989        }
990
991        try
992        {
993            if ( ( ioSession != null ) && ioSession.isConnected() )
994            { 
995                connectionCloseFuture.get( timeout, TimeUnit.MILLISECONDS );
996            }
997        }
998        catch ( TimeoutException | ExecutionException | InterruptedException e )
999        {
1000            if ( LOG.isDebugEnabled() )
1001            {
1002                LOG.debug( I18n.msg( I18n.MSH_04178_CLOSE_LATCH_ABORTED ) );
1003            }
1004        }
1005    }
1006
1007
1008    //------------------------ The LDAP operations ------------------------//
1009    // Add operations                                                      //
1010    //---------------------------------------------------------------------//
1011    /**
1012     * {@inheritDoc}
1013     */
1014    @Override
1015    public void add( Entry entry ) throws LdapException
1016    {
1017        if ( entry == null )
1018        {
1019            String msg = I18n.err( I18n.ERR_04123_CANNOT_ADD_EMPTY_ENTRY );
1020            
1021            if ( LOG.isDebugEnabled() )
1022            {
1023                LOG.debug( msg );
1024            }
1025            
1026            throw new IllegalArgumentException( msg );
1027        }
1028
1029        AddRequest addRequest = new AddRequestImpl();
1030        addRequest.setEntry( entry );
1031
1032        AddResponse addResponse = add( addRequest );
1033
1034        processResponse( addResponse );
1035    }
1036
1037
1038    /**
1039     * {@inheritDoc}
1040     */
1041    @Override
1042    public AddFuture addAsync( Entry entry ) throws LdapException
1043    {
1044        if ( entry == null )
1045        {
1046            String msg = I18n.err( I18n.ERR_04125_CANNOT_ADD_NULL_ENTRY );
1047            
1048            if ( LOG.isDebugEnabled() )
1049            {
1050                LOG.debug( msg );
1051            }
1052            
1053            throw new IllegalArgumentException( msg );
1054        }
1055
1056        AddRequest addRequest = new AddRequestImpl();
1057        addRequest.setEntry( entry );
1058
1059        return addAsync( addRequest );
1060    }
1061
1062
1063    /**
1064     * {@inheritDoc}
1065     */
1066    @Override
1067    public AddResponse add( AddRequest addRequest ) throws LdapException
1068    {
1069        if ( addRequest == null )
1070        {
1071            String msg = I18n.err( I18n.ERR_04124_CANNOT_PROCESS_NULL_ADD_REQUEST );
1072
1073            if ( LOG.isDebugEnabled() )
1074            {
1075                LOG.debug( msg );
1076            }
1077            
1078            throw new IllegalArgumentException( msg );
1079        }
1080
1081        if ( addRequest.getEntry() == null )
1082        {
1083            String msg = I18n.err( I18n.ERR_04125_CANNOT_ADD_NULL_ENTRY );
1084
1085            if ( LOG.isDebugEnabled() )
1086            {
1087                LOG.debug( msg );
1088            }
1089            
1090            throw new IllegalArgumentException( msg );
1091        }
1092
1093        AddFuture addFuture = addAsync( addRequest );
1094
1095        // Get the result from the future
1096        try
1097        {
1098            // Read the response, waiting for it if not available immediately
1099            // Get the response, blocking
1100            AddResponse addResponse = addFuture.get( timeout, TimeUnit.MILLISECONDS );
1101
1102            if ( addResponse == null )
1103            {
1104                // We didn't received anything : this is an error
1105                if ( LOG.isErrorEnabled() )
1106                {
1107                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Add" ) );
1108                }
1109                
1110                throw new LdapException( TIME_OUT_ERROR );
1111            }
1112
1113            if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1114            {
1115                // Everything is fine, return the response
1116                if ( LOG.isDebugEnabled() )
1117                { 
1118                    LOG.debug( I18n.msg( I18n.MSG_04108_ADD_SUCCESSFUL, addResponse ) );
1119                }
1120            }
1121            else
1122            {
1123                // We have had an error
1124                if ( LOG.isDebugEnabled() )
1125                { 
1126                    LOG.debug( I18n.msg( I18n.MSG_04107_ADD_FAILED, addResponse ) );
1127                }
1128            }
1129
1130            return addResponse;
1131        }
1132        catch ( Exception ie )
1133        {
1134            // Catch all other exceptions
1135            LOG.error( NO_RESPONSE_ERROR, ie );
1136
1137            // Send an abandon request
1138            if ( !addFuture.isCancelled() )
1139            {
1140                abandon( addRequest.getMessageId() );
1141            }
1142
1143            throw new LdapException( NO_RESPONSE_ERROR, ie );
1144        }
1145    }
1146
1147
1148    /**
1149     * {@inheritDoc}
1150     */
1151    @Override
1152    public AddFuture addAsync( AddRequest addRequest ) throws LdapException
1153    {
1154        if ( addRequest == null )
1155        {
1156            String msg = I18n.err( I18n.ERR_04124_CANNOT_PROCESS_NULL_ADD_REQUEST );
1157
1158            if ( LOG.isDebugEnabled() )
1159            {
1160                LOG.debug( msg );
1161            }
1162            
1163            throw new IllegalArgumentException( msg );
1164        }
1165
1166        if ( addRequest.getEntry() == null )
1167        {
1168            String msg = I18n.err( I18n.ERR_04125_CANNOT_ADD_NULL_ENTRY );
1169
1170            if ( LOG.isDebugEnabled() )
1171            {
1172                LOG.debug( msg );
1173            }
1174            
1175            throw new IllegalArgumentException( msg );
1176        }
1177
1178        // try to connect, if we aren't already connected.
1179        connect();
1180
1181        checkSession();
1182
1183        int newId = messageId.incrementAndGet();
1184
1185        addRequest.setMessageId( newId );
1186        AddFuture addFuture = new AddFuture( this, newId );
1187        addToFutureMap( newId, addFuture );
1188
1189        // Send the request to the server
1190        writeRequest( addRequest );
1191
1192        // Ok, done return the future
1193        return addFuture;
1194    }
1195
1196
1197    //------------------------ The LDAP operations ------------------------//
1198
1199    /**
1200     * {@inheritDoc}
1201     */
1202    @Override
1203    public void abandon( int messageId )
1204    {
1205        if ( messageId < 0 )
1206        {
1207            String msg = I18n.err( I18n.ERR_04126_CANNOT_ABANDON_NEG_MSG_ID );
1208
1209            if ( LOG.isDebugEnabled() )
1210            {
1211                LOG.debug( msg );
1212            }
1213            
1214            throw new IllegalArgumentException( msg );
1215        }
1216
1217        AbandonRequest abandonRequest = new AbandonRequestImpl();
1218        abandonRequest.setAbandoned( messageId );
1219
1220        abandonInternal( abandonRequest );
1221    }
1222
1223
1224    /**
1225     * {@inheritDoc}
1226     */
1227    @Override
1228    public void abandon( AbandonRequest abandonRequest )
1229    {
1230        if ( abandonRequest == null )
1231        {
1232            String msg = I18n.err( I18n.ERR_04127_CANNOT_PROCESS_NULL_ABANDON_REQ );
1233
1234            if ( LOG.isDebugEnabled() )
1235            {
1236                LOG.debug( msg );
1237            }
1238            
1239            throw new IllegalArgumentException( msg );
1240        }
1241
1242        abandonInternal( abandonRequest );
1243    }
1244
1245
1246    /**
1247     * Internal AbandonRequest handling
1248     * 
1249     * @param abandonRequest The request to abandon
1250     */
1251    private void abandonInternal( AbandonRequest abandonRequest )
1252    {
1253        if ( LOG.isDebugEnabled() )
1254        {
1255            LOG.debug( I18n.msg( I18n.MSG_04104_SENDING_REQUEST, abandonRequest ) );
1256        }
1257
1258        int newId = messageId.incrementAndGet();
1259        abandonRequest.setMessageId( newId );
1260
1261        // Send the request to the server
1262        ioSession.write( abandonRequest );
1263
1264        // remove the associated listener if any
1265        int abandonId = abandonRequest.getAbandoned();
1266
1267        ResponseFuture<? extends Response> rf = getFromFutureMap( abandonId );
1268
1269        // if the listener is not null, this is a async operation and no need to
1270        // send cancel signal on future, sending so will leave a dangling poision object in the corresponding queue
1271        // this is a sync operation send cancel signal to the corresponding ResponseFuture
1272        if ( rf != null )
1273        {
1274            if ( LOG.isDebugEnabled() )
1275            {
1276                LOG.debug( I18n.msg( I18n.MSG_04141_SENDING_CANCEL ) );
1277            }
1278            
1279            rf.cancel( true );
1280        }
1281        else
1282        {
1283            // this shouldn't happen
1284            if ( LOG.isWarnEnabled() )
1285            {
1286                LOG.warn( I18n.msg( I18n.MSG_04165_NO_FUTURE_ASSOCIATED_TO_MSG_ID_COMPLETED, abandonId ) );
1287            }
1288        }
1289    }
1290
1291
1292    /**
1293     * {@inheritDoc}
1294     */
1295    @Override
1296    public void bind() throws LdapException
1297    {
1298        if ( LOG.isDebugEnabled() )
1299        {
1300            LOG.debug( I18n.msg(  I18n.MSG_04112_BIND ) );
1301        }
1302
1303        // Create the BindRequest
1304        BindRequest bindRequest = createBindRequest( config.getName(), Strings.getBytesUtf8( config.getCredentials() ) );
1305
1306        BindResponse bindResponse = bind( bindRequest );
1307
1308        processResponse( bindResponse );
1309    }
1310
1311
1312    /**
1313     * {@inheritDoc}
1314     */
1315    @Override
1316    public void anonymousBind() throws LdapException
1317    {
1318        if ( LOG.isDebugEnabled() )
1319        { 
1320            LOG.debug( I18n.msg( I18n.MSG_04109_ANONYMOUS_BIND ) );
1321        }
1322
1323        // Create the BindRequest
1324        BindRequest bindRequest = createBindRequest( StringConstants.EMPTY, Strings.EMPTY_BYTES );
1325
1326        BindResponse bindResponse = bind( bindRequest );
1327
1328        processResponse( bindResponse );
1329    }
1330
1331
1332    /**
1333     * {@inheritDoc}
1334     */
1335    @Override
1336    public BindFuture bindAsync() throws LdapException
1337    {
1338        if ( LOG.isDebugEnabled() )
1339        {
1340            LOG.debug( I18n.msg( I18n.MSG_04111_ASYNC_BIND ) );
1341        }
1342
1343        // Create the BindRequest
1344        BindRequest bindRequest = createBindRequest( config.getName(), Strings.getBytesUtf8( config.getCredentials() ) );
1345
1346        return bindAsync( bindRequest );
1347    }
1348
1349
1350    /**
1351     * {@inheritDoc}
1352     */
1353    @Override
1354    public BindFuture anonymousBindAsync() throws LdapException
1355    {
1356        if ( LOG.isDebugEnabled() )
1357        { 
1358            LOG.debug( I18n.msg( I18n.MSG_04110_ANONYMOUS_ASYNC_BIND ) );
1359        }
1360
1361        // Create the BindRequest
1362        BindRequest bindRequest = createBindRequest( StringConstants.EMPTY, Strings.EMPTY_BYTES );
1363
1364        return bindAsync( bindRequest );
1365    }
1366
1367
1368    /**
1369     * Asynchronous unauthenticated authentication bind
1370     *
1371     * @param name The name we use to authenticate the user. It must be a
1372     * valid Dn
1373     * @return The BindResponse LdapResponse
1374     * @throws LdapException if some error occurred
1375     */
1376    public BindFuture bindAsync( String name ) throws LdapException
1377    {
1378        if ( LOG.isDebugEnabled() )
1379        {
1380            LOG.debug( I18n.msg( I18n.MSG_04102_BIND_REQUEST, name ) );
1381        }
1382
1383        // Create the BindRequest
1384        BindRequest bindRequest = createBindRequest( name, Strings.EMPTY_BYTES );
1385
1386        return bindAsync( bindRequest );
1387    }
1388
1389
1390    /**
1391     * {@inheritDoc}
1392     */
1393    @Override
1394    public BindFuture bindAsync( String name, String credentials ) throws LdapException
1395    {
1396        if ( LOG.isDebugEnabled() )
1397        {
1398            LOG.debug( I18n.msg( I18n.MSG_04102_BIND_REQUEST, name ) );
1399        }
1400
1401        // The password must not be empty or null
1402        if ( Strings.isEmpty( credentials ) && Strings.isNotEmpty( name ) )
1403        {
1404            if ( LOG.isDebugEnabled() )
1405            {
1406                LOG.debug( I18n.msg( I18n.MSG_04105_MISSING_PASSWORD ) );
1407            }
1408            
1409            throw new LdapAuthenticationException( I18n.msg( I18n.MSG_04105_MISSING_PASSWORD ) );
1410        }
1411
1412        // Create the BindRequest
1413        BindRequest bindRequest = createBindRequest( name, Strings.getBytesUtf8( credentials ) );
1414
1415        return bindAsync( bindRequest );
1416    }
1417
1418
1419    /**
1420     * Asynchronous unauthenticated authentication Bind on a server.
1421     *
1422     * @param name The name we use to authenticate the user. It must be a
1423     * valid Dn
1424     * @return The BindResponse LdapResponse
1425     * @throws LdapException if some error occurred
1426     */
1427    public BindFuture bindAsync( Dn name ) throws LdapException
1428    {
1429        if ( LOG.isDebugEnabled() )
1430        {
1431            LOG.debug( I18n.msg( I18n.MSG_04102_BIND_REQUEST, name ) );
1432        }
1433
1434        // Create the BindRequest
1435        BindRequest bindRequest = createBindRequest( name, Strings.EMPTY_BYTES );
1436
1437        return bindAsync( bindRequest );
1438    }
1439
1440
1441    /**
1442     * {@inheritDoc}
1443     */
1444    @Override
1445    public BindFuture bindAsync( Dn name, String credentials ) throws LdapException
1446    {
1447        if ( LOG.isDebugEnabled() )
1448        {
1449            LOG.debug( I18n.msg( I18n.MSG_04102_BIND_REQUEST, name ) );
1450        }
1451
1452        // The password must not be empty or null
1453        if ( Strings.isEmpty( credentials ) && ( !Dn.EMPTY_DN.equals( name ) ) )
1454        {
1455            if ( LOG.isDebugEnabled() )
1456            {
1457                LOG.debug( I18n.msg( I18n.MSG_04105_MISSING_PASSWORD ) );
1458            }
1459            
1460            throw new LdapAuthenticationException( I18n.msg( I18n.MSG_04105_MISSING_PASSWORD ) );
1461        }
1462
1463        // Create the BindRequest
1464        BindRequest bindRequest = createBindRequest( name, Strings.getBytesUtf8( credentials ) );
1465
1466        return bindAsync( bindRequest );
1467    }
1468
1469
1470    /**
1471     * {@inheritDoc}
1472     */
1473    @Override
1474    public BindResponse bind( BindRequest bindRequest ) throws LdapException
1475    {
1476        if ( bindRequest == null )
1477        {
1478            String msg = I18n.err( I18n.ERR_04128_CANNOT_PROCESS_NULL_BIND_REQ );
1479
1480            if ( LOG.isDebugEnabled() )
1481            {
1482                LOG.debug( msg );
1483            }
1484            
1485            throw new IllegalArgumentException( msg );
1486        }
1487
1488        BindFuture bindFuture = bindAsync( bindRequest );
1489
1490        // Get the result from the future
1491        try
1492        {
1493            // Read the response, waiting for it if not available immediately
1494            // Get the response, blocking
1495            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1496
1497            if ( bindResponse == null )
1498            {
1499                // We didn't received anything : this is an error
1500                if ( LOG.isErrorEnabled() )
1501                { 
1502                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1503                }
1504                
1505                throw new LdapException( TIME_OUT_ERROR );
1506            }
1507
1508            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1509            {
1510                authenticated.set( true );
1511
1512                // Everything is fine, return the response
1513                if ( LOG.isDebugEnabled() )
1514                { 
1515                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1516                }
1517            }
1518            else
1519            {
1520                // We have had an error
1521                if ( LOG.isDebugEnabled() )
1522                { 
1523                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1524                }
1525            }
1526
1527            return bindResponse;
1528        }
1529        catch ( Exception ie )
1530        {
1531            // Catch all other exceptions
1532            LOG.error( NO_RESPONSE_ERROR, ie );
1533            
1534            throw new LdapException( NO_RESPONSE_ERROR, ie );
1535        }
1536    }
1537
1538
1539    /**
1540     * Create a Simple BindRequest ready to be sent.
1541     * 
1542     * @param name The Bind name
1543     * @param credentials The Bind credentials
1544     * @return The created BindRequest instance
1545     */
1546    private BindRequest createBindRequest( String name, byte[] credentials )
1547    {
1548        return createBindRequest( name, credentials, null, ( Control[] ) null );
1549    }
1550
1551
1552    /**
1553     * Create a Simple BindRequest ready to be sent.
1554     * 
1555     * @param name The Bind name
1556     * @param credentials The Bind credentials
1557     * @return The created BindRequest instance
1558     */
1559    private BindRequest createBindRequest( Dn name, byte[] credentials )
1560    {
1561        return createBindRequest( name.getName(), credentials, null, ( Control[] ) null );
1562    }
1563
1564
1565    /**
1566     * {@inheritDoc}
1567     */
1568    @Override
1569    public BindFuture bindAsync( BindRequest bindRequest ) throws LdapException
1570    {
1571        if ( bindRequest == null )
1572        {
1573            String msg = I18n.err( I18n.ERR_04128_CANNOT_PROCESS_NULL_BIND_REQ );
1574
1575            if ( LOG.isDebugEnabled() )
1576            {
1577                LOG.debug( msg );
1578            }
1579            
1580            throw new IllegalArgumentException( msg );
1581        }
1582
1583        // First switch to anonymous state
1584        authenticated.set( false );
1585
1586        // try to connect, if we aren't already connected.
1587        connect();
1588
1589        // establish TLS layer if TLS is enabled and SSL is NOT
1590        if ( config.isUseTls() && !config.isUseSsl() )
1591        {
1592            startTls();
1593        }
1594
1595        // If the session has not been establish, or is closed, we get out immediately
1596        checkSession();
1597
1598        // Update the messageId
1599        int newId = messageId.incrementAndGet();
1600        bindRequest.setMessageId( newId );
1601
1602        if ( LOG.isDebugEnabled() )
1603        {
1604            LOG.debug( I18n.msg( I18n.MSG_04104_SENDING_REQUEST, bindRequest ) );
1605        }
1606
1607        // Create a future for this Bind operation
1608        BindFuture bindFuture = new BindFuture( this, newId );
1609
1610        addToFutureMap( newId, bindFuture );
1611
1612        writeRequest( bindRequest );
1613
1614        // Ok, done return the future
1615        return bindFuture;
1616    }
1617
1618
1619    /**
1620     * SASL PLAIN Bind on a server.
1621     *
1622     * @param authcid The Authentication identity
1623     * @param credentials The password. It can't be null
1624     * @return The BindResponse LdapResponse
1625     * @throws LdapException if some error occurred
1626     */
1627    public BindResponse bindSaslPlain( String authcid, String credentials ) throws LdapException
1628    {
1629        return bindSaslPlain( null, authcid, credentials );
1630    }
1631
1632
1633    /**
1634     * SASL PLAIN Bind on a server.
1635     *
1636     * @param authzid The Authorization identity
1637     * @param authcid The Authentication identity
1638     * @param credentials The password. It can't be null
1639     * @return The BindResponse LdapResponse
1640     * @throws LdapException if some error occurred
1641     */
1642    public BindResponse bindSaslPlain( String authzid, String authcid, String credentials ) throws LdapException
1643    {
1644        if ( LOG.isDebugEnabled() )
1645        {
1646            LOG.debug( I18n.msg( I18n.MSG_04127_SASL_PLAIN_BIND ) );
1647        }
1648
1649        // Create the BindRequest
1650        SaslPlainRequest saslRequest = new SaslPlainRequest();
1651        saslRequest.setAuthorizationId( authzid );
1652        saslRequest.setUsername( authcid );
1653        saslRequest.setCredentials( credentials );
1654
1655        BindFuture bindFuture = bindAsync( saslRequest );
1656
1657        // Get the result from the future
1658        try
1659        {
1660            // Read the response, waiting for it if not available immediately
1661            // Get the response, blocking
1662            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1663
1664            if ( bindResponse == null )
1665            {
1666                // We didn't received anything : this is an error
1667                if ( LOG.isErrorEnabled() )
1668                { 
1669                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1670                }
1671                
1672                throw new LdapException( TIME_OUT_ERROR );
1673            }
1674
1675            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1676            {
1677                authenticated.set( true );
1678
1679                // Everything is fine, return the response
1680                if ( LOG.isDebugEnabled() )
1681                { 
1682                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1683                }
1684            }
1685            else
1686            {
1687                // We have had an error
1688                if ( LOG.isDebugEnabled() )
1689                { 
1690                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1691                }
1692            }
1693
1694            return bindResponse;
1695        }
1696        catch ( Exception ie )
1697        {
1698            // Catch all other exceptions
1699            LOG.error( NO_RESPONSE_ERROR, ie );
1700
1701            throw new LdapException( NO_RESPONSE_ERROR, ie );
1702        }
1703    }
1704
1705
1706    /**
1707     * Bind to the server using a SaslRequest object.
1708     *
1709     * @param request The SaslRequest POJO containing all the needed parameters
1710     * @return A LdapResponse containing the result
1711     * @throws LdapException if some error occurred
1712     */
1713    public BindResponse bind( SaslRequest request ) throws LdapException
1714    {
1715        if ( request == null )
1716        {
1717            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
1718
1719            if ( LOG.isDebugEnabled() )
1720            {
1721                LOG.debug( msg );
1722            }
1723            
1724            throw new IllegalArgumentException( msg );
1725        }
1726
1727        BindFuture bindFuture = bindAsync( request );
1728
1729        // Get the result from the future
1730        try
1731        {
1732            // Read the response, waiting for it if not available immediately
1733            // Get the response, blocking
1734            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1735
1736            if ( bindResponse == null )
1737            {
1738                // We didn't received anything : this is an error
1739                if ( LOG.isErrorEnabled() )
1740                { 
1741                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1742                }
1743                
1744                throw new LdapException( TIME_OUT_ERROR );
1745            }
1746
1747            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1748            {
1749                authenticated.set( true );
1750
1751                // Everything is fine, return the response
1752                if ( LOG.isDebugEnabled() )
1753                { 
1754                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1755                }
1756            }
1757            else
1758            {
1759                // We have had an error
1760                if ( LOG.isDebugEnabled() )
1761                { 
1762                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1763                }
1764            }
1765
1766            return bindResponse;
1767        }
1768        catch ( Exception ie )
1769        {
1770            // Catch all other exceptions
1771            LOG.error( NO_RESPONSE_ERROR, ie );
1772
1773            throw new LdapException( NO_RESPONSE_ERROR, ie );
1774        }
1775    }
1776
1777
1778    /**
1779     * Bind to the server using the SASL CRAM-MD5 mechanism.
1780     *
1781     * @param userName The user name
1782     * @param credentials The user credentials
1783     * @return  A LdapResponse containing the result
1784     * @throws LdapException if some error occurred
1785     */
1786    public BindResponse bindSaslCramMd5( String userName, String credentials ) throws LdapException
1787    {
1788        SaslCramMd5Request request = new SaslCramMd5Request();
1789        request.setUsername( userName );
1790        request.setCredentials( "secret" );
1791
1792        return bind( request );
1793    }
1794
1795
1796    /**
1797     * Bind to the server using the SASL DIGEST-MD5 mechanism.
1798     *
1799     * @param userName The user name
1800     * @param credentials The user credentials
1801     * @return  A LdapResponse containing the result
1802     * @throws LdapException if some error occurred
1803     */
1804    public BindResponse bindSaslDigestMd5( String userName, String credentials ) throws LdapException
1805    {
1806        SaslDigestMd5Request request = new SaslDigestMd5Request();
1807        request.setUsername( userName );
1808        request.setCredentials( "secret" );
1809
1810        return bind( request );
1811    }
1812
1813
1814    /**
1815     * Bind to the server using a CramMd5Request object.
1816     *
1817     * @param request The CramMd5Request POJO containing all the needed parameters
1818     * @return A LdapResponse containing the result
1819     * @throws LdapException if some error occurred
1820     */
1821    public BindResponse bind( SaslCramMd5Request request ) throws LdapException
1822    {
1823        if ( request == null )
1824        {
1825            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
1826
1827            if ( LOG.isDebugEnabled() )
1828            {
1829                LOG.debug( msg );
1830            }
1831            
1832            throw new IllegalArgumentException( msg );
1833        }
1834
1835        BindFuture bindFuture = bindAsync( request );
1836
1837        // Get the result from the future
1838        try
1839        {
1840            // Read the response, waiting for it if not available immediately
1841            // Get the response, blocking
1842            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1843
1844            if ( bindResponse == null )
1845            {
1846                // We didn't received anything : this is an error
1847                if ( LOG.isErrorEnabled() )
1848                { 
1849                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1850                }
1851                
1852                throw new LdapException( TIME_OUT_ERROR );
1853            }
1854
1855            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1856            {
1857                authenticated.set( true );
1858
1859                // Everything is fine, return the response
1860                if ( LOG.isDebugEnabled() )
1861                { 
1862                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1863                }
1864            }
1865            else
1866            {
1867                // We have had an error
1868                if ( LOG.isDebugEnabled() )
1869                { 
1870                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1871                }
1872            }
1873
1874            return bindResponse;
1875        }
1876        catch ( Exception ie )
1877        {
1878            // Catch all other exceptions
1879            LOG.error( NO_RESPONSE_ERROR, ie );
1880
1881            throw new LdapException( NO_RESPONSE_ERROR, ie );
1882        }
1883    }
1884
1885
1886    /**
1887     * Do an asynchronous bind, based on a SaslPlainRequest.
1888     *
1889     * @param request The SaslPlainRequest POJO containing all the needed parameters
1890     * @return The bind operation's future
1891     * @throws LdapException if some error occurred
1892     */
1893    public BindFuture bindAsync( SaslRequest request )
1894        throws LdapException
1895    {
1896        return bindSasl( request );
1897    }
1898
1899
1900    /**
1901     * Bind to the server using a DigestMd5Request object.
1902     *
1903     * @param request The DigestMd5Request POJO containing all the needed parameters
1904     * @return A LdapResponse containing the result
1905     * @throws LdapException if some error occurred
1906     */
1907    public BindResponse bind( SaslDigestMd5Request request ) throws LdapException
1908    {
1909        if ( request == null )
1910        {
1911            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
1912
1913            if ( LOG.isDebugEnabled() )
1914            {
1915                LOG.debug( msg );
1916            }
1917            
1918            throw new IllegalArgumentException( msg );
1919        }
1920
1921        BindFuture bindFuture = bindAsync( request );
1922
1923        // Get the result from the future
1924        try
1925        {
1926            // Read the response, waiting for it if not available immediately
1927            // Get the response, blocking
1928            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1929
1930            if ( bindResponse == null )
1931            {
1932                // We didn't received anything : this is an error
1933                if ( LOG.isErrorEnabled() )
1934                { 
1935                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1936                }
1937                
1938                throw new LdapException( TIME_OUT_ERROR );
1939            }
1940
1941            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1942            {
1943                authenticated.set( true );
1944
1945                // Everything is fine, return the response
1946                if ( LOG.isDebugEnabled() )
1947                { 
1948                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1949                }
1950            }
1951            else
1952            {
1953                // We have had an error
1954                if ( LOG.isDebugEnabled() )
1955                { 
1956                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1957                }
1958            }
1959
1960            return bindResponse;
1961        }
1962        catch ( Exception ie )
1963        {
1964            // Catch all other exceptions
1965            LOG.error( NO_RESPONSE_ERROR, ie );
1966
1967            throw new LdapException( NO_RESPONSE_ERROR, ie );
1968        }
1969    }
1970
1971
1972    /**
1973     * Bind to the server using a GssApiRequest object.
1974     *
1975     * @param request The GssApiRequest POJO containing all the needed parameters
1976     * @return A LdapResponse containing the result
1977     * @throws LdapException if some error occurred
1978     */
1979    public BindResponse bind( SaslGssApiRequest request ) throws LdapException
1980    {
1981        if ( request == null )
1982        {
1983            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
1984
1985            if ( LOG.isDebugEnabled() )
1986            {
1987                LOG.debug( msg );
1988            }
1989            
1990            throw new IllegalArgumentException( msg );
1991        }
1992
1993        BindFuture bindFuture = bindAsync( request );
1994
1995        // Get the result from the future
1996        try
1997        {
1998            // Read the response, waiting for it if not available immediately
1999            // Get the response, blocking
2000            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
2001
2002            if ( bindResponse == null )
2003            {
2004                // We didn't received anything : this is an error
2005                if ( LOG.isErrorEnabled() )
2006                { 
2007                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
2008                }
2009                
2010                throw new LdapException( TIME_OUT_ERROR );
2011            }
2012
2013            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2014            {
2015                authenticated.set( true );
2016
2017                // Everything is fine, return the response
2018                if ( LOG.isDebugEnabled() )
2019                { 
2020                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
2021                }
2022            }
2023            else
2024            {
2025                // We have had an error
2026                if ( LOG.isDebugEnabled() )
2027                { 
2028                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
2029                }
2030            }
2031
2032            return bindResponse;
2033        }
2034        catch ( Exception ie )
2035        {
2036            // Catch all other exceptions
2037            LOG.error( NO_RESPONSE_ERROR, ie );
2038
2039            throw new LdapException( NO_RESPONSE_ERROR, ie );
2040        }
2041    }
2042
2043
2044    /**
2045     * Bind to the server using a SaslExternalRequest object.
2046     *
2047     * @param request The SaslExternalRequest POJO containing all the needed parameters
2048     * @return A LdapResponse containing the result
2049     * @throws LdapException if some error occurred
2050     */
2051    public BindResponse bind( SaslExternalRequest request ) throws LdapException
2052    {
2053        if ( request == null )
2054        {
2055            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
2056
2057            if ( LOG.isDebugEnabled() )
2058            {
2059                LOG.debug( msg );
2060            }
2061            
2062            throw new IllegalArgumentException( msg );
2063        }
2064
2065        BindFuture bindFuture = bindAsync( request );
2066
2067        // Get the result from the future
2068        try
2069        {
2070            // Read the response, waiting for it if not available immediately
2071            // Get the response, blocking
2072            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
2073
2074            if ( bindResponse == null )
2075            {
2076                // We didn't received anything : this is an error
2077                if ( LOG.isErrorEnabled() )
2078                { 
2079                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
2080                }
2081                
2082                throw new LdapException( TIME_OUT_ERROR );
2083            }
2084
2085            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2086            {
2087                authenticated.set( true );
2088
2089                // Everything is fine, return the response
2090                if ( LOG.isDebugEnabled() )
2091                { 
2092                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
2093                }
2094            }
2095            else
2096            {
2097                // We have had an error
2098                if ( LOG.isDebugEnabled() )
2099                { 
2100                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
2101                }
2102            }
2103
2104            return bindResponse;
2105        }
2106        catch ( Exception ie )
2107        {
2108            // Catch all other exceptions
2109            LOG.error( NO_RESPONSE_ERROR, ie );
2110
2111            throw new LdapException( NO_RESPONSE_ERROR, ie );
2112        }
2113    }
2114
2115
2116    /**
2117     * Do an asynchronous bind, based on a GssApiRequest.
2118     *
2119     * @param request The GssApiRequest POJO containing all the needed parameters
2120     * @return The bind operation's future
2121     * @throws LdapException if some error occurred
2122     */
2123    public BindFuture bindAsync( SaslGssApiRequest request )
2124        throws LdapException
2125    {
2126        // Krb5.conf file
2127        if ( request.getKrb5ConfFilePath() != null )
2128        {
2129            // Using the krb5.conf file provided by the user
2130            System.setProperty( KRB5_CONF, request.getKrb5ConfFilePath() );
2131        }
2132        else if ( ( request.getRealmName() != null ) && ( request.getKdcHost() != null )
2133            && ( request.getKdcPort() != 0 ) )
2134        {
2135            try
2136            {
2137                // Using a custom krb5.conf we create from the settings provided by the user
2138                String krb5ConfPath = createKrb5ConfFile( request.getRealmName(), request.getKdcHost(),
2139                    request.getKdcPort() );
2140                System.setProperty( KRB5_CONF, krb5ConfPath );
2141            }
2142            catch ( IOException ioe )
2143            {
2144                throw new LdapException( ioe );
2145            }
2146        }
2147        else
2148        {
2149            // Using the system Kerberos configuration
2150            System.clearProperty( KRB5_CONF );
2151        }
2152
2153        // Login Module configuration
2154        if ( request.getLoginModuleConfiguration() != null )
2155        {
2156            // Using the configuration provided by the user
2157            Configuration.setConfiguration( request.getLoginModuleConfiguration() );
2158        }
2159        else
2160        {
2161            // Using the default configuration
2162            Configuration.setConfiguration( new Krb5LoginConfiguration() );
2163        }
2164
2165        try
2166        {
2167            System.setProperty( "javax.security.auth.useSubjectCredsOnly", "true" );
2168            LoginContext loginContext = new LoginContext( request.getLoginContextName(),
2169                new SaslCallbackHandler( request ) );
2170            loginContext.login();
2171
2172            final SaslGssApiRequest requetFinal = request;
2173            return ( BindFuture ) Subject.doAs( loginContext.getSubject(), new PrivilegedExceptionAction<Object>()
2174            {
2175                @Override
2176                public Object run() throws Exception
2177                {
2178                    return bindSasl( requetFinal );
2179                }
2180            } );
2181        }
2182        catch ( Exception e )
2183        {
2184            connectionCloseFuture.complete( 0 );
2185            throw new LdapException( e );
2186        }
2187    }
2188
2189
2190    /**
2191     * {@inheritDoc}
2192     */
2193    @Override
2194    public EntryCursor search( Dn baseDn, String filter, SearchScope scope, String... attributes )
2195        throws LdapException
2196    {
2197        if ( baseDn == null )
2198        {
2199            if ( LOG.isDebugEnabled() )
2200            {
2201                LOG.debug( I18n.msg( I18n.MSG_04138_NULL_DN_SEARCH ) );
2202            }
2203            
2204            throw new IllegalArgumentException( I18n.err( I18n.ERR_04129_NULL_BASE_DN ) );
2205        }
2206
2207        // Create a new SearchRequest object
2208        SearchRequest searchRequest = new SearchRequestImpl();
2209
2210        searchRequest.setBase( baseDn );
2211        searchRequest.setFilter( filter );
2212        searchRequest.setScope( scope );
2213        searchRequest.addAttributes( attributes );
2214        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
2215
2216        // Process the request in blocking mode
2217        return new EntryCursorImpl( search( searchRequest ) );
2218    }
2219
2220
2221    /**
2222     * {@inheritDoc}
2223     */
2224    @Override
2225    public EntryCursor search( String baseDn, String filter, SearchScope scope, String... attributes )
2226        throws LdapException
2227    {
2228        return search( new Dn( baseDn ), filter, scope, attributes );
2229    }
2230
2231
2232    /**
2233     * {@inheritDoc}
2234     */
2235    @Override
2236    public SearchFuture searchAsync( Dn baseDn, String filter, SearchScope scope, String... attributes )
2237        throws LdapException
2238    {
2239        // Create a new SearchRequest object
2240        SearchRequest searchRequest = new SearchRequestImpl();
2241
2242        searchRequest.setBase( baseDn );
2243        searchRequest.setFilter( filter );
2244        searchRequest.setScope( scope );
2245        searchRequest.addAttributes( attributes );
2246        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
2247
2248        // Process the request in blocking mode
2249        return searchAsync( searchRequest );
2250    }
2251
2252
2253    /**
2254     * {@inheritDoc}
2255     */
2256    @Override
2257    public SearchFuture searchAsync( String baseDn, String filter, SearchScope scope, String... attributes )
2258        throws LdapException
2259    {
2260        return searchAsync( new Dn( baseDn ), filter, scope, attributes );
2261    }
2262
2263
2264    /**
2265     * {@inheritDoc}
2266     */
2267    @Override
2268    public SearchFuture searchAsync( SearchRequest searchRequest ) throws LdapException
2269    {
2270        if ( searchRequest == null )
2271        {
2272            String msg = I18n.err( I18n.ERR_04130_CANNOT_PROCESS_NULL_SEARCH_REQ );
2273
2274            if ( LOG.isDebugEnabled() )
2275            {
2276                LOG.debug( msg );
2277            }
2278            
2279            throw new IllegalArgumentException( msg );
2280        }
2281
2282        if ( searchRequest.getBase() == null )
2283        {
2284            String msg = I18n.err( I18n.ERR_04131_CANNOT_PROCESS_SEARCH_NULL_DN );
2285
2286            if ( LOG.isDebugEnabled() )
2287            {
2288                LOG.debug( msg );
2289            }
2290            
2291            throw new IllegalArgumentException( msg );
2292        }
2293
2294        // try to connect, if we aren't already connected.
2295        connect();
2296
2297        // If the session has not been establish, or is closed, we get out immediately
2298        checkSession();
2299
2300        int newId = messageId.incrementAndGet();
2301        searchRequest.setMessageId( newId );
2302
2303        if ( searchRequest.isIgnoreReferrals() )
2304        {
2305            // We want to ignore the referral, inject the ManageDSAIT control in the request
2306            searchRequest.addControl( new ManageDsaITImpl() );
2307        }
2308
2309        if ( LOG.isDebugEnabled() )
2310        {
2311            LOG.debug( I18n.msg( I18n.MSG_04104_SENDING_REQUEST, searchRequest ) );
2312        }
2313
2314        SearchFuture searchFuture = new SearchFuture( this, searchRequest.getMessageId() );
2315        addToFutureMap( searchRequest.getMessageId(), searchFuture );
2316
2317        // Send the request to the server
2318        writeRequest( searchRequest );
2319
2320        // Check that the future hasn't be canceled
2321        if ( searchFuture.isCancelled() )
2322        {
2323            // Throw an exception here
2324            throw new LdapException( searchFuture.getCause() );
2325        }
2326
2327        // Ok, done return the future
2328        return searchFuture;
2329    }
2330
2331
2332    /**
2333     * {@inheritDoc}
2334     */
2335    @Override
2336    public SearchCursor search( SearchRequest searchRequest ) throws LdapException
2337    {
2338        if ( searchRequest == null )
2339        {
2340            String msg = I18n.err( I18n.ERR_04130_CANNOT_PROCESS_NULL_SEARCH_REQ );
2341            
2342            if ( LOG.isDebugEnabled() )
2343            {
2344                LOG.debug( msg );
2345            }
2346            
2347            throw new IllegalArgumentException( msg );
2348        }
2349
2350        SearchFuture searchFuture = searchAsync( searchRequest );
2351
2352        long searchTimeout = getTimeout( timeout, searchRequest.getTimeLimit() );
2353
2354        return new SearchCursorImpl( searchFuture, searchTimeout, TimeUnit.MILLISECONDS );
2355    }
2356
2357
2358    //------------------------ The LDAP operations ------------------------//
2359    // Unbind operations                                                   //
2360    //---------------------------------------------------------------------//
2361    /**
2362     * {@inheritDoc}
2363     */
2364    @Override
2365    public void unBind() throws LdapException
2366    {
2367        // If the session has not been establish, or is closed, we get out immediately
2368        checkSession();
2369
2370        // Creates the messageID and stores it into the
2371        // initial message and the transmitted message.
2372        int newId = messageId.incrementAndGet();
2373
2374        // Create the UnbindRequest
2375        UnbindRequest unbindRequest = new UnbindRequestImpl();
2376        unbindRequest.setMessageId( newId );
2377
2378        if ( LOG.isDebugEnabled() )
2379        {
2380            LOG.debug( I18n.msg( I18n.MSG_04132_SENDING_UNBIND, unbindRequest ) );
2381        }
2382
2383        // Send the request to the server
2384        // Use this for logging instead: WriteFuture unbindFuture = ldapSession.write( unbindRequest )
2385        WriteFuture unbindFuture = ioSession.write( unbindRequest );
2386
2387        unbindFuture.awaitUninterruptibly( timeout );
2388
2389        try
2390        {
2391            connectionCloseFuture.get( timeout, TimeUnit.MILLISECONDS );
2392        }
2393        catch ( TimeoutException | ExecutionException | InterruptedException e )
2394        {
2395            if ( LOG.isDebugEnabled() )
2396            {
2397                LOG.debug( I18n.msg( I18n.MSH_04178_CLOSE_LATCH_ABORTED ) );
2398            }
2399        }
2400
2401        // And get out
2402        if ( LOG.isDebugEnabled() )
2403        {
2404            LOG.debug( I18n.msg( I18n.MSG_04133_UNBINDSUCCESSFUL ) );
2405        }
2406    }
2407
2408
2409    /**
2410     * Set the connector to use.
2411     *
2412     * @param connector The connector to use
2413     */
2414    public void setConnector( IoConnector connector )
2415    {
2416        this.connector = connector;
2417    }
2418
2419
2420    /**
2421     * {@inheritDoc}
2422     */
2423    @Override
2424    public void setTimeOut( long timeout )
2425    {
2426        if ( timeout <= 0 )
2427        {
2428            // Set a date in the far future : 100 years
2429            this.timeout = 1000L * 60L * 60L * 24L * 365L * 100L;
2430        }
2431        else
2432        {
2433            this.timeout = timeout;
2434        }
2435    }
2436
2437
2438    /**
2439     * Handle the exception we got.
2440     *
2441     * @param session The session we got the exception on
2442     * @param cause The exception cause
2443     * @throws Exception If we have had another exception
2444     */
2445    @Override
2446    public void exceptionCaught( IoSession session, Throwable cause ) throws Exception
2447    {
2448        if ( LOG.isWarnEnabled() )
2449        {
2450            LOG.warn( cause.getMessage(), cause );
2451        }
2452
2453        session.setAttribute( EXCEPTION_KEY, cause );
2454
2455        if ( cause instanceof ProtocolEncoderException )
2456        {
2457            Throwable realCause = ( ( ProtocolEncoderException ) cause ).getCause();
2458
2459            if ( realCause instanceof MessageEncoderException )
2460            {
2461                int messageId = ( ( MessageEncoderException ) realCause ).getMessageId();
2462
2463                ResponseFuture<?> response = futureMap.get( messageId );
2464                response.cancel( true );
2465                response.setCause( realCause );
2466            }
2467        }
2468
2469        session.closeNow();
2470    }
2471
2472
2473    /**
2474     * Check if the message is a NoticeOfDisconnect message
2475     * 
2476     * @param message The message to check
2477     * @return <tt>true</tt> if the message is a Notice of Disconnect
2478     */
2479    private boolean isNoticeOfDisconnect( Message message )
2480    {
2481        if ( message instanceof ExtendedResponse )
2482        {
2483            String responseName = ( ( ExtendedResponse ) message ).getResponseName();
2484
2485            if ( NoticeOfDisconnect.EXTENSION_OID.equals( responseName ) )
2486            {
2487                return true;
2488            }
2489        }
2490
2491        return false;
2492    }
2493
2494
2495    /**
2496     * Process the AddResponse received from the server
2497     * 
2498     * @param addResponse The AddResponse to process
2499     * @param addFuture The AddFuture to feed
2500     * @param responseId The associated request message ID
2501     * @throws InterruptedException If the Future is interrupted
2502     */
2503    private void addReceived( AddResponse addResponse, AddFuture addFuture, int responseId ) throws InterruptedException
2504    {
2505        // remove the listener from the listener map
2506        if ( LOG.isDebugEnabled() )
2507        {
2508            if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2509            {
2510                // Everything is fine, return the response
2511                LOG.debug( I18n.msg( I18n.MSG_04108_ADD_SUCCESSFUL, addResponse ) );
2512            }
2513            else
2514            {
2515                // We have had an error
2516                LOG.debug( I18n.msg( I18n.MSG_04107_ADD_FAILED, addResponse ) );
2517            }
2518        }
2519
2520        // Store the response into the future
2521        addFuture.set( addResponse );
2522
2523        // Remove the future from the map
2524        removeFromFutureMaps( responseId );
2525    }
2526
2527
2528    /**
2529     * Process the BindResponse received from the server
2530     * 
2531     * @param bindResponse The BindResponse to process
2532     * @param bindFuture The BindFuture to feed
2533     * @param responseId The associated request message ID
2534     * @throws InterruptedException If the Future is interrupted
2535     */
2536    private void bindReceived( BindResponse bindResponse, BindFuture bindFuture, int responseId ) 
2537        throws InterruptedException
2538    {
2539        // remove the listener from the listener map
2540        if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2541        {
2542            authenticated.set( true );
2543
2544            // Everything is fine, return the response
2545            if ( LOG.isDebugEnabled() )
2546            { 
2547                LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
2548            }
2549        }
2550        else
2551        {
2552            // We have had an error
2553            if ( LOG.isDebugEnabled() )
2554            { 
2555                LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
2556            }
2557        }
2558
2559        // Store the response into the future
2560        bindFuture.set( bindResponse );
2561
2562        // Remove the future from the map
2563        removeFromFutureMaps( responseId );
2564    }
2565
2566
2567    /**
2568     * Process the CompareResponse received from the server
2569     * 
2570     * @param compareResponse The CompareResponse to process
2571     * @param compareFuture The CompareFuture to feed
2572     * @param responseId The associated request message ID
2573     * @throws InterruptedException If the Future is interrupted
2574     */
2575    private void compareReceived( CompareResponse compareResponse, CompareFuture compareFuture, int responseId ) 
2576       throws InterruptedException
2577    {
2578        // remove the listener from the listener map
2579        if ( LOG.isDebugEnabled() )
2580        {
2581            if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2582            {
2583                // Everything is fine, return the response
2584                LOG.debug( I18n.msg( I18n.MSG_04114_COMPARE_SUCCESSFUL, compareResponse ) );
2585            }
2586            else
2587            {
2588                // We have had an error
2589                LOG.debug( I18n.msg( I18n.MSG_04113_COMPARE_FAILED, compareResponse ) );
2590            }
2591        }
2592
2593        // Store the response into the future
2594        compareFuture.set( compareResponse );
2595
2596        // Remove the future from the map
2597        removeFromFutureMaps( responseId );
2598    }
2599
2600
2601    /**
2602     * Process the DeleteResponse received from the server
2603     * 
2604     * @param deleteResponse The DeleteResponse to process
2605     * @param deleteFuture The DeleteFuture to feed
2606     * @param responseId The associated request message ID
2607     * @throws InterruptedException If the Future is interrupted
2608     */
2609    private void deleteReceived( DeleteResponse deleteResponse, DeleteFuture deleteFuture, int responseId ) 
2610        throws InterruptedException
2611    {
2612        if ( LOG.isDebugEnabled() )
2613        {
2614            if ( deleteResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2615            {
2616                // Everything is fine, return the response
2617                LOG.debug( I18n.msg( I18n.MSG_04116_DELETE_SUCCESSFUL, deleteResponse ) );
2618            }
2619            else
2620            {
2621                // We have had an error
2622                LOG.debug( I18n.msg( I18n.MSG_04115_DELETE_FAILED, deleteResponse ) );
2623            }
2624        }
2625
2626        // Store the response into the future
2627        deleteFuture.set( deleteResponse );
2628
2629        // Remove the future from the map
2630        removeFromFutureMaps( responseId );
2631    }
2632
2633
2634    /**
2635     * Process the ExtendedResponse received from the server
2636     * 
2637     * @param extendedResponse The ExtendedResponse to process
2638     * @param extendedFuture The ExtendedFuture to feed
2639     * @param responseId The associated request message ID
2640     * @throws InterruptedException If the Future is interrupted
2641     * @throws DecoderException If the response cannot be decoded
2642     */
2643    private void extendedReceived( ExtendedResponse extendedResponse, ExtendedFuture extendedFuture, int responseId ) 
2644        throws InterruptedException, DecoderException
2645    {
2646        if ( LOG.isDebugEnabled() )
2647        {
2648            if ( extendedResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2649            {
2650                // Everything is fine, return the response
2651                LOG.debug( I18n.msg( I18n.MSG_04118_EXTENDED_SUCCESSFUL, extendedResponse ) );
2652            }
2653            else
2654            {
2655                // We have had an error
2656                LOG.debug( I18n.msg( I18n.MSG_04117_EXTENDED_FAILED, extendedResponse ) );
2657            }
2658        }
2659        
2660        extendedResponse = handleOpaqueResponse( extendedResponse, extendedFuture );
2661
2662        // Store the response into the future
2663        extendedFuture.set( extendedResponse );
2664
2665        // Remove the future from the map
2666        removeFromFutureMaps( responseId );
2667    }
2668
2669
2670    /**
2671     * Process the IntermediateResponse received from the server
2672     * 
2673     * @param intermediateResponse The IntermediateResponse to process
2674     * @param responseFuture The ResponseFuture to feed
2675     * @throws InterruptedException If the Future is interrupted
2676     */
2677    private void intermediateReceived( IntermediateResponse intermediateResponse, ResponseFuture<? extends Response> responseFuture ) 
2678        throws InterruptedException
2679    {
2680        // Store the response into the future
2681        if ( responseFuture instanceof SearchFuture )
2682        {
2683            ( ( SearchFuture ) responseFuture ).set( intermediateResponse );
2684        }
2685        else if ( responseFuture instanceof ExtendedFuture )
2686        {
2687            ( ( ExtendedFuture ) responseFuture ).set( intermediateResponse );
2688        }
2689        else
2690        {
2691            // currently we only support IR for search and extended operations
2692            throw new UnsupportedOperationException( I18n.err( I18n.ERR_04111_UNKNOWN_RESPONSE_FUTURE_TYPE,
2693                responseFuture.getClass().getName() ) );
2694        }
2695
2696        // Do not remove the future from the map, that's done when receiving search result done
2697    }
2698
2699
2700    /**
2701     * Process the ModifyResponse received from the server
2702     * 
2703     * @param modifyResponse The ModifyResponse to process
2704     * @param modifyFuture The ModifyFuture to feed
2705     * @param responseId The associated request message ID
2706     * @throws InterruptedException If the Future is interrupted
2707     */
2708    private void modifyReceived( ModifyResponse modifyResponse, ModifyFuture modifyFuture, int responseId ) 
2709        throws InterruptedException
2710    {
2711        if ( LOG.isDebugEnabled() )
2712        {
2713            if ( modifyResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2714            {
2715                // Everything is fine, return the response
2716                if ( LOG.isDebugEnabled() )
2717                { 
2718                    LOG.debug( I18n.msg( I18n.MSG_04123_MODIFY_SUCCESSFUL, modifyResponse ) );
2719                }
2720            }
2721            else
2722            {
2723                // We have had an error
2724                if ( LOG.isDebugEnabled() )
2725                { 
2726                    LOG.debug( I18n.msg( I18n.MSG_04122_MODIFY_FAILED, modifyResponse ) );
2727                }
2728            }
2729        }
2730
2731        // Store the response into the future
2732        modifyFuture.set( modifyResponse );
2733
2734        // Remove the future from the map
2735        removeFromFutureMaps( responseId );
2736    }
2737
2738
2739    /**
2740     * Process the ModifyDnResponse received from the server
2741     * 
2742     * @param modifyDnResponse The ModifyDnResponse to process
2743     * @param modifyDnFuture The ModifyDnFuture to feed
2744     * @param responseId The associated request message ID
2745     * @throws InterruptedException If the Future is interrupted
2746     */
2747    private void modifyDnReceived( ModifyDnResponse modifyDnResponse, ModifyDnFuture modifyDnFuture, int responseId ) 
2748        throws InterruptedException
2749    {
2750        if ( LOG.isDebugEnabled() )
2751        {
2752            if ( modifyDnResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2753            {
2754                // Everything is fine, return the response
2755                LOG.debug( I18n.msg( I18n.MSG_04125_MODIFYDN_SUCCESSFUL, modifyDnResponse ) );
2756            }
2757            else
2758            {
2759                // We have had an error
2760                LOG.debug( I18n.msg( I18n.MSG_04124_MODIFYDN_FAILED, modifyDnResponse ) );
2761            }
2762        }
2763
2764        // Store the response into the future
2765        modifyDnFuture.set( modifyDnResponse );
2766
2767        // Remove the future from the map
2768        removeFromFutureMaps( responseId );
2769    }
2770
2771
2772    /**
2773     * Process the SearchResultDone received from the server
2774     * 
2775     * @param searchResultDone The SearchResultDone to process
2776     * @param searchFuture The SearchFuture to feed
2777     * @param responseId The associated request message ID
2778     * @throws InterruptedException If the Future is interrupted
2779     */
2780    private void searchResultDoneReceived( SearchResultDone searchResultDone, SearchFuture searchFuture, 
2781        int responseId ) throws InterruptedException
2782    {
2783        if ( LOG.isDebugEnabled() )
2784        {
2785            if ( searchResultDone.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2786            {
2787                // Everything is fine, return the response
2788                LOG.debug( I18n.msg( I18n.MSG_04131_SEARCH_SUCCESSFUL, searchResultDone ) );
2789            }
2790            else
2791            {
2792                // We have had an error
2793                LOG.debug( I18n.msg( I18n.MSG_04129_SEARCH_FAILED, searchResultDone ) );
2794            }
2795        }
2796
2797        // Store the response into the future
2798        searchFuture.set( searchResultDone );
2799
2800        // Remove the future from the map
2801        removeFromFutureMaps( responseId );
2802    }
2803
2804
2805    /**
2806     * Process the SearchResultEntry received from the server
2807     * 
2808     * @param searchResultEntry The SearchResultEntry to process
2809     * @param searchFuture The SearchFuture to feed
2810     * @throws InterruptedException If the Future is interrupted
2811     * @throws LdapException If we weren't able to create a new Entry
2812     */
2813    private void searchResultEntryReceived( SearchResultEntry searchResultEntry, SearchFuture searchFuture ) 
2814        throws InterruptedException, LdapException
2815    {
2816        if ( schemaManager != null )
2817        {
2818            searchResultEntry.setEntry( new DefaultEntry( schemaManager, searchResultEntry.getEntry() ) );
2819        }
2820
2821        if ( LOG.isDebugEnabled() )
2822        {
2823            LOG.debug( I18n.msg( I18n.MSG_04128_SEARCH_ENTRY_FOUND, searchResultEntry ) );
2824        }
2825
2826        // Store the response into the future
2827        searchFuture.set( searchResultEntry );
2828    }
2829    
2830    
2831    /**
2832     * Process the SearchResultEntry received from the server
2833     * 
2834     * @param searchResultReference The SearchResultReference to process
2835     * @param searchFuture The SearchFuture to feed
2836     * @throws InterruptedException If the Future is interrupted
2837     */
2838    private void searchResultReferenceReceived( SearchResultReference searchResultReference, SearchFuture searchFuture ) 
2839        throws InterruptedException
2840    {
2841        if ( LOG.isDebugEnabled() )
2842        {
2843            LOG.debug( I18n.msg( I18n.MSG_04130_SEARCH_REFERENCE_FOUND, searchResultReference ) );
2844        }
2845
2846        // Store the response into the future
2847        searchFuture.set( searchResultReference );
2848    }
2849    
2850
2851    /**
2852     * Handle the incoming LDAP messages. This is where we feed the cursor for search
2853     * requests, or call the listener.
2854     *
2855     * @param session The session that received a message
2856     * @param message The received message
2857     * @throws Exception If there is some error while processing the message
2858     */
2859    @Override
2860    public void messageReceived( IoSession session, Object message ) throws Exception
2861    {
2862        // Feed the response and store it into the session
2863        Response response = ( Response ) message;
2864
2865        if ( LOG.isDebugEnabled() )
2866        {
2867            LOG.debug( I18n.msg( I18n.MSG_04142_MESSAGE_RECEIVED, response ) );
2868        }
2869        
2870        int responseId = response.getMessageId();
2871
2872        // this check is necessary to prevent adding an abandoned operation's
2873        // result(s) to corresponding queue
2874        ResponseFuture<? extends Response> responseFuture = peekFromFutureMap( responseId );
2875
2876        boolean isNoD = isNoticeOfDisconnect( response );
2877
2878        if ( ( responseFuture == null ) && !isNoD )
2879        {
2880            if ( LOG.isInfoEnabled() )
2881            {
2882                LOG.info( I18n.msg( I18n.MSG_04166_NO_FUTURE_ASSOCIATED_TO_MSG_ID_IGNORING, responseId ) );
2883            }
2884            
2885            return;
2886        }
2887
2888        if ( isNoD )
2889        {
2890            // close the session
2891            session.closeNow();
2892
2893            return;
2894        }
2895
2896        switch ( response.getType() )
2897        {
2898            case ADD_RESPONSE:
2899                addReceived( ( AddResponse ) response, ( AddFuture ) responseFuture, responseId );
2900
2901                break;
2902
2903            case BIND_RESPONSE:
2904                bindReceived( ( BindResponse ) response, ( BindFuture ) responseFuture, responseId );
2905
2906                break;
2907
2908            case COMPARE_RESPONSE:
2909                compareReceived( ( CompareResponse ) response, ( CompareFuture ) responseFuture, responseId );
2910
2911                break;
2912
2913            case DEL_RESPONSE:
2914                deleteReceived( ( DeleteResponse ) response, ( DeleteFuture ) responseFuture, responseId );
2915
2916                break;
2917
2918            case EXTENDED_RESPONSE:
2919                extendedReceived( ( ExtendedResponse ) response, ( ExtendedFuture ) responseFuture, responseId );
2920
2921                break;
2922
2923            case INTERMEDIATE_RESPONSE:
2924                intermediateReceived( ( IntermediateResponse ) response, responseFuture );
2925
2926                break;
2927
2928            case MODIFY_RESPONSE:
2929                modifyReceived( ( ModifyResponse ) response, ( ModifyFuture ) responseFuture, responseId );
2930
2931                break;
2932
2933            case MODIFYDN_RESPONSE:
2934                modifyDnReceived( ( ModifyDnResponse ) response, ( ModifyDnFuture ) responseFuture, responseId );
2935
2936                break;
2937
2938            case SEARCH_RESULT_DONE:
2939                searchResultDoneReceived( ( SearchResultDone ) response, ( SearchFuture ) responseFuture, responseId );
2940
2941                break;
2942
2943            case SEARCH_RESULT_ENTRY:
2944                searchResultEntryReceived( ( SearchResultEntry ) response, ( SearchFuture ) responseFuture );
2945
2946                break;
2947
2948            case SEARCH_RESULT_REFERENCE:
2949                searchResultReferenceReceived( ( SearchResultReference ) response, ( SearchFuture ) responseFuture );
2950
2951                break;
2952
2953            default:
2954                throw new IllegalStateException( I18n.err( I18n.ERR_04132_UNEXPECTED_RESPONSE_TYPE, response.getType() ) );
2955        }
2956    }
2957
2958    
2959    private ExtendedResponse handleOpaqueResponse( ExtendedResponse extendedResponse, ExtendedFuture extendedFuture ) 
2960        throws DecoderException
2961    {
2962        if ( ( extendedResponse instanceof OpaqueExtendedResponse ) 
2963            && ( Strings.isEmpty( extendedResponse.getResponseName() ) ) ) 
2964        {
2965            ExtendedOperationFactory factory = codec.getExtendedResponseFactories().
2966                get( extendedFuture.getExtendedRequest().getRequestName() );
2967
2968            byte[] responseValue = ( ( OpaqueExtendedResponse ) extendedResponse ).getResponseValue();
2969
2970            ExtendedResponse response;
2971            if ( responseValue != null )
2972            {
2973                response = factory.newResponse( responseValue );
2974            }
2975            else
2976            {
2977                response = factory.newResponse();
2978            }
2979
2980            // Copy the controls
2981            for ( Control control : extendedResponse.getControls().values() )
2982            {
2983                response.addControl( control );
2984            }
2985            
2986            // copy the LDAPResult
2987            response.getLdapResult().setDiagnosticMessage( extendedResponse.getLdapResult().getDiagnosticMessage() );
2988            response.getLdapResult().setMatchedDn( extendedResponse.getLdapResult().getMatchedDn() );
2989            response.getLdapResult().setReferral( extendedResponse.getLdapResult().getReferral() );
2990            response.getLdapResult().setResultCode( extendedResponse.getLdapResult().getResultCode() );
2991            
2992            return response;
2993        }
2994        else
2995        {
2996            return extendedResponse;
2997        }
2998    }
2999
3000    /**
3001     * {@inheritDoc}
3002     */
3003    @Override
3004    public void modify( Entry entry, ModificationOperation modOp ) throws LdapException
3005    {
3006        if ( entry == null )
3007        {
3008            if ( LOG.isDebugEnabled() )
3009            {
3010                LOG.debug( I18n.msg( I18n.MSG_04140_NULL_ENTRY_MODIFY ) );
3011            }
3012            
3013            throw new IllegalArgumentException( I18n.err( I18n.ERR_04133_NULL_MODIFIED_ENTRY ) );
3014        }
3015
3016        ModifyRequest modReq = new ModifyRequestImpl();
3017        modReq.setName( entry.getDn() );
3018
3019        Iterator<Attribute> itr = entry.iterator();
3020
3021        while ( itr.hasNext() )
3022        {
3023            modReq.addModification( itr.next(), modOp );
3024        }
3025
3026        ModifyResponse modifyResponse = modify( modReq );
3027
3028        processResponse( modifyResponse );
3029    }
3030
3031
3032    /**
3033     * {@inheritDoc}
3034     */
3035    @Override
3036    public void modify( Dn dn, Modification... modifications ) throws LdapException
3037    {
3038        if ( dn == null )
3039        {
3040            if ( LOG.isDebugEnabled() )
3041            {
3042                LOG.debug( I18n.msg( I18n.MSG_04139_NULL_DN_MODIFY ) );
3043            }
3044            
3045            throw new IllegalArgumentException( I18n.err( I18n.ERR_04134_NULL_MODIFIED_DN ) );
3046        }
3047
3048        if ( ( modifications == null ) || ( modifications.length == 0 ) )
3049        {
3050            String msg = I18n.err( I18n.ERR_04135_CANNOT_PROCESS_NO_MODIFICATION_MOD );
3051
3052            if ( LOG.isDebugEnabled() )
3053            {
3054                LOG.debug( msg );
3055            }
3056            
3057            throw new IllegalArgumentException( msg );
3058        }
3059
3060        ModifyRequest modReq = new ModifyRequestImpl();
3061        modReq.setName( dn );
3062
3063        for ( Modification modification : modifications )
3064        {
3065            modReq.addModification( modification );
3066        }
3067
3068        ModifyResponse modifyResponse = modify( modReq );
3069
3070        processResponse( modifyResponse );
3071    }
3072
3073
3074    /**
3075     * {@inheritDoc}
3076     */
3077    @Override
3078    public void modify( String dn, Modification... modifications ) throws LdapException
3079    {
3080        modify( new Dn( dn ), modifications );
3081    }
3082
3083
3084    /**
3085     * {@inheritDoc}
3086     */
3087    @Override
3088    public ModifyResponse modify( ModifyRequest modRequest ) throws LdapException
3089    {
3090        if ( modRequest == null )
3091        {
3092            String msg = I18n.err( I18n.ERR_04136_CANNOT_PROCESS_NULL_MOD_REQ );
3093
3094            if ( LOG.isDebugEnabled() )
3095            {
3096                LOG.debug( msg );
3097            }
3098            
3099            throw new IllegalArgumentException( msg );
3100        }
3101
3102        ModifyFuture modifyFuture = modifyAsync( modRequest );
3103
3104        // Get the result from the future
3105        try
3106        {
3107            // Read the response, waiting for it if not available immediately
3108            // Get the response, blocking
3109            ModifyResponse modifyResponse = modifyFuture.get( timeout, TimeUnit.MILLISECONDS );
3110
3111            if ( modifyResponse == null )
3112            {
3113                // We didn't received anything : this is an error
3114                if ( LOG.isErrorEnabled() )
3115                {
3116                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Modify" ) );
3117                }
3118                
3119                throw new LdapException( TIME_OUT_ERROR );
3120            }
3121
3122            if ( modifyResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3123            {
3124                // Everything is fine, return the response
3125                if ( LOG.isDebugEnabled() )
3126                { 
3127                    LOG.debug( I18n.msg( I18n.MSG_04123_MODIFY_SUCCESSFUL, modifyResponse ) );
3128                }
3129            }
3130            else
3131            {
3132                if ( modifyResponse instanceof ModifyNoDResponse )
3133                {
3134                    // A NoticeOfDisconnect : deserves a special treatment
3135                    throw new LdapException( modifyResponse.getLdapResult().getDiagnosticMessage() );
3136                }
3137
3138                // We have had an error
3139                if ( LOG.isDebugEnabled() )
3140                { 
3141                    LOG.debug( I18n.msg( I18n.MSG_04122_MODIFY_FAILED, modifyResponse ) );
3142                }
3143            }
3144
3145            return modifyResponse;
3146        }
3147        catch ( Exception ie )
3148        {
3149            // Catch all other exceptions
3150            LOG.error( NO_RESPONSE_ERROR, ie );
3151
3152            // Send an abandon request
3153            if ( !modifyFuture.isCancelled() )
3154            {
3155                abandon( modRequest.getMessageId() );
3156            }
3157
3158            throw new LdapException( ie.getMessage(), ie );
3159        }
3160    }
3161
3162
3163    /**
3164     * {@inheritDoc}
3165     */
3166    @Override
3167    public ModifyFuture modifyAsync( ModifyRequest modRequest ) throws LdapException
3168    {
3169        if ( modRequest == null )
3170        {
3171            String msg = I18n.err( I18n.ERR_04136_CANNOT_PROCESS_NULL_MOD_REQ );
3172
3173            if ( LOG.isDebugEnabled() )
3174            {
3175                LOG.debug( msg );
3176            }
3177            
3178            throw new IllegalArgumentException( msg );
3179        }
3180
3181        if ( modRequest.getName() == null )
3182        {
3183            String msg = I18n.err( I18n.ERR_04137_CANNOT_PROCESS_MOD_NULL_DN );
3184
3185            if ( LOG.isDebugEnabled() )
3186            {
3187                LOG.debug( msg );
3188            }
3189            
3190            throw new IllegalArgumentException( msg );
3191        }
3192
3193        // try to connect, if we aren't already connected.
3194        connect();
3195
3196        checkSession();
3197
3198        int newId = messageId.incrementAndGet();
3199        modRequest.setMessageId( newId );
3200
3201        ModifyFuture modifyFuture = new ModifyFuture( this, newId );
3202        addToFutureMap( newId, modifyFuture );
3203
3204        // Send the request to the server
3205        writeRequest( modRequest );
3206
3207        // Ok, done return the future
3208        return modifyFuture;
3209    }
3210
3211
3212    /**
3213     * {@inheritDoc}
3214     */
3215    @Override
3216    public void rename( String entryDn, String newRdn ) throws LdapException
3217    {
3218        rename( entryDn, newRdn, true );
3219    }
3220
3221
3222    /**
3223     * {@inheritDoc}
3224     */
3225    @Override
3226    public void rename( Dn entryDn, Rdn newRdn ) throws LdapException
3227    {
3228        rename( entryDn, newRdn, true );
3229    }
3230
3231
3232    /**
3233     * {@inheritDoc}
3234     */
3235    @Override
3236    public void rename( String entryDn, String newRdn, boolean deleteOldRdn ) throws LdapException
3237    {
3238        if ( entryDn == null )
3239        {
3240            String msg = I18n.err( I18n.ERR_04138_CANNOT_PROCESS_RENAME_NULL_DN );
3241
3242            if ( LOG.isDebugEnabled() )
3243            {
3244                LOG.debug( msg );
3245            }
3246            
3247            throw new IllegalArgumentException( msg );
3248        }
3249
3250        if ( newRdn == null )
3251        {
3252            String msg = I18n.err( I18n.ERR_04139_CANNOT_PROCESS_RENAME_NULL_RDN );
3253
3254            if ( LOG.isDebugEnabled() )
3255            {
3256                LOG.debug( msg );
3257            }
3258            
3259            throw new IllegalArgumentException( msg );
3260        }
3261
3262        try
3263        {
3264            rename( new Dn( entryDn ), new Rdn( newRdn ), deleteOldRdn );
3265        }
3266        catch ( LdapInvalidDnException e )
3267        {
3268            LOG.error( e.getMessage(), e );
3269            throw new LdapException( e.getMessage(), e );
3270        }
3271    }
3272
3273
3274    /**
3275     * {@inheritDoc}
3276     */
3277    @Override
3278    public void rename( Dn entryDn, Rdn newRdn, boolean deleteOldRdn ) throws LdapException
3279    {
3280        if ( entryDn == null )
3281        {
3282            String msg = I18n.err( I18n.ERR_04138_CANNOT_PROCESS_RENAME_NULL_DN );
3283
3284            if ( LOG.isDebugEnabled() )
3285            {
3286                LOG.debug( msg );
3287            }
3288            
3289            throw new IllegalArgumentException( msg );
3290        }
3291
3292        if ( newRdn == null )
3293        {
3294            String msg = I18n.err( I18n.ERR_04139_CANNOT_PROCESS_RENAME_NULL_RDN );
3295            
3296            if ( LOG.isDebugEnabled() )
3297            {
3298                LOG.debug( msg );
3299            }
3300            
3301            throw new IllegalArgumentException( msg );
3302        }
3303
3304        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
3305        modDnRequest.setName( entryDn );
3306        modDnRequest.setNewRdn( newRdn );
3307        modDnRequest.setDeleteOldRdn( deleteOldRdn );
3308
3309        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
3310
3311        processResponse( modifyDnResponse );
3312    }
3313
3314
3315    /**
3316     * {@inheritDoc}
3317     */
3318    @Override
3319    public void move( String entryDn, String newSuperiorDn ) throws LdapException
3320    {
3321        if ( entryDn == null )
3322        {
3323            String msg = I18n.err( I18n.ERR_04140_CANNOT_PROCESS_MOVE_NULL_DN );
3324
3325            if ( LOG.isDebugEnabled() )
3326            {
3327                LOG.debug( msg );
3328            }
3329            
3330            throw new IllegalArgumentException( msg );
3331        }
3332
3333        if ( newSuperiorDn == null )
3334        {
3335            String msg = I18n.err( I18n.ERR_04141_CANNOT_PROCESS_MOVE_NULL_SUPERIOR );
3336            
3337            if ( LOG.isDebugEnabled() )
3338            {
3339                LOG.debug( msg );
3340            }
3341            
3342            throw new IllegalArgumentException( msg );
3343        }
3344
3345        try
3346        {
3347            move( new Dn( entryDn ), new Dn( newSuperiorDn ) );
3348        }
3349        catch ( LdapInvalidDnException e )
3350        {
3351            LOG.error( e.getMessage(), e );
3352            throw new LdapException( e.getMessage(), e );
3353        }
3354    }
3355
3356
3357    /**
3358     * {@inheritDoc}
3359     */
3360    @Override
3361    public void move( Dn entryDn, Dn newSuperiorDn ) throws LdapException
3362    {
3363        if ( entryDn == null )
3364        {
3365            String msg = I18n.err( I18n.ERR_04140_CANNOT_PROCESS_MOVE_NULL_DN );
3366
3367            if ( LOG.isDebugEnabled() )
3368            {
3369                LOG.debug( msg );
3370            }
3371            
3372            throw new IllegalArgumentException( msg );
3373        }
3374
3375        if ( newSuperiorDn == null )
3376        {
3377            String msg = I18n.err( I18n.ERR_04141_CANNOT_PROCESS_MOVE_NULL_SUPERIOR );
3378
3379            if ( LOG.isDebugEnabled() )
3380            {
3381                LOG.debug( msg );
3382            }
3383            
3384            throw new IllegalArgumentException( msg );
3385        }
3386
3387        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
3388        modDnRequest.setName( entryDn );
3389        modDnRequest.setNewSuperior( newSuperiorDn );
3390
3391        modDnRequest.setNewRdn( entryDn.getRdn() );
3392
3393        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
3394
3395        processResponse( modifyDnResponse );
3396    }
3397
3398
3399    /**
3400     * {@inheritDoc}
3401     */
3402    @Override
3403    public void moveAndRename( Dn entryDn, Dn newDn ) throws LdapException
3404    {
3405        moveAndRename( entryDn, newDn, true );
3406    }
3407
3408
3409    /**
3410     * {@inheritDoc}
3411     */
3412    @Override
3413    public void moveAndRename( String entryDn, String newDn ) throws LdapException
3414    {
3415        moveAndRename( new Dn( entryDn ), new Dn( newDn ), true );
3416    }
3417
3418
3419    /**
3420     * {@inheritDoc}
3421     */
3422    @Override
3423    public void moveAndRename( Dn entryDn, Dn newDn, boolean deleteOldRdn ) throws LdapException
3424    {
3425        // Check the parameters first
3426        if ( entryDn == null )
3427        {
3428            throw new IllegalArgumentException( I18n.err( I18n.ERR_04142_NULL_ENTRY_DN ) );
3429        }
3430
3431        if ( entryDn.isRootDse() )
3432        {
3433            throw new IllegalArgumentException( I18n.err( I18n.ERR_04143_CANNOT_MOVE_ROOT_DSE ) );
3434        }
3435
3436        if ( newDn == null )
3437        {
3438            throw new IllegalArgumentException( I18n.err( I18n.ERR_04144_NULL_NEW_DN ) );
3439        }
3440
3441        if ( newDn.isRootDse() )
3442        {
3443            throw new IllegalArgumentException( I18n.err( I18n.ERR_04145_ROOT_DSE_CANNOT_BE_TARGET ) );
3444        }
3445
3446        // Create the request
3447        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
3448        modDnRequest.setName( entryDn );
3449        modDnRequest.setNewRdn( newDn.getRdn() );
3450        
3451        // Check if we really need to specify newSuperior.
3452        // newSuperior is optional [RFC4511, section 4.9]
3453        // Some servers (e.g. OpenDJ 2.6) require a special privilege if
3454        // newSuperior is specified even if it is the same as the old one. Therefore let's not
3455        // specify it if we do not need it. This is better interoperability. 
3456        Dn newDnParent = newDn.getParent();
3457        if ( newDnParent != null && !newDnParent.equals( entryDn.getParent() ) )
3458        {
3459            modDnRequest.setNewSuperior( newDnParent );
3460        }
3461        
3462        modDnRequest.setDeleteOldRdn( deleteOldRdn );
3463
3464        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
3465
3466        processResponse( modifyDnResponse );
3467    }
3468
3469
3470    /**
3471     * {@inheritDoc}
3472     */
3473    @Override
3474    public void moveAndRename( String entryDn, String newDn, boolean deleteOldRdn ) throws LdapException
3475    {
3476        moveAndRename( new Dn( entryDn ), new Dn( newDn ), true );
3477    }
3478
3479
3480    /**
3481     * {@inheritDoc}
3482     */
3483    @Override
3484    public ModifyDnResponse modifyDn( ModifyDnRequest modDnRequest ) throws LdapException
3485    {
3486        if ( modDnRequest == null )
3487        {
3488            String msg = I18n.err( I18n.ERR_04145_ROOT_DSE_CANNOT_BE_TARGET );
3489
3490            if ( LOG.isDebugEnabled() )
3491            {
3492                LOG.debug( msg );
3493            }
3494            
3495            throw new IllegalArgumentException( msg );
3496        }
3497
3498        ModifyDnFuture modifyDnFuture = modifyDnAsync( modDnRequest );
3499
3500        // Get the result from the future
3501        try
3502        {
3503            // Read the response, waiting for it if not available immediately
3504            // Get the response, blocking
3505            ModifyDnResponse modifyDnResponse = modifyDnFuture.get( timeout, TimeUnit.MILLISECONDS );
3506
3507            if ( modifyDnResponse == null )
3508            {
3509                // We didn't received anything : this is an error
3510                if ( LOG.isErrorEnabled() )
3511                {
3512                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "ModifyDn" ) );
3513                }
3514                
3515                throw new LdapException( TIME_OUT_ERROR );
3516            }
3517
3518            if ( modifyDnResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3519            {
3520                // Everything is fine, return the response
3521                if ( LOG.isDebugEnabled() )
3522                { 
3523                    LOG.debug( I18n.msg( I18n.MSG_04125_MODIFYDN_SUCCESSFUL, modifyDnResponse ) );
3524                }
3525            }
3526            else
3527            {
3528                // We have had an error
3529                if ( LOG.isDebugEnabled() )
3530                { 
3531                    LOG.debug( I18n.msg( I18n.MSG_04124_MODIFYDN_FAILED, modifyDnResponse ) );
3532                }
3533            }
3534
3535            return modifyDnResponse;
3536        }
3537        catch ( Exception ie )
3538        {
3539            // Catch all other exceptions
3540            LOG.error( NO_RESPONSE_ERROR, ie );
3541
3542            // Send an abandon request
3543            if ( !modifyDnFuture.isCancelled() )
3544            {
3545                abandon( modDnRequest.getMessageId() );
3546            }
3547
3548            throw new LdapException( NO_RESPONSE_ERROR, ie );
3549        }
3550    }
3551
3552
3553    /**
3554     * {@inheritDoc}
3555     */
3556    @Override
3557    public ModifyDnFuture modifyDnAsync( ModifyDnRequest modDnRequest ) throws LdapException
3558    {
3559        if ( modDnRequest == null )
3560        {
3561            String msg = I18n.err( I18n.ERR_04145_ROOT_DSE_CANNOT_BE_TARGET );
3562
3563            if ( LOG.isDebugEnabled() )
3564            {
3565                LOG.debug( msg );
3566            }
3567            
3568            throw new IllegalArgumentException( msg );
3569        }
3570
3571        if ( modDnRequest.getName() == null )
3572        {
3573            String msg = I18n.err( I18n.ERR_04137_CANNOT_PROCESS_MOD_NULL_DN );
3574
3575            if ( LOG.isDebugEnabled() )
3576            {
3577                LOG.debug( msg );
3578            }
3579            
3580            throw new IllegalArgumentException( msg );
3581        }
3582
3583        if ( ( modDnRequest.getNewSuperior() == null ) && ( modDnRequest.getNewRdn() == null ) )
3584        {
3585            String msg = I18n.err( I18n.ERR_04147_CANNOT_PROCESS_MOD_NULL_DN_SUP );
3586
3587            if ( LOG.isDebugEnabled() )
3588            {
3589                LOG.debug( msg );
3590            }
3591            
3592            throw new IllegalArgumentException( msg );
3593        }
3594
3595        // try to connect, if we aren't already connected.
3596        connect();
3597
3598        checkSession();
3599
3600        int newId = messageId.incrementAndGet();
3601        modDnRequest.setMessageId( newId );
3602
3603        ModifyDnFuture modifyDnFuture = new ModifyDnFuture( this, newId );
3604        addToFutureMap( newId, modifyDnFuture );
3605
3606        // Send the request to the server
3607        writeRequest( modDnRequest );
3608
3609        // Ok, done return the future
3610        return modifyDnFuture;
3611    }
3612
3613
3614    /**
3615     * {@inheritDoc}
3616     */
3617    @Override
3618    public void delete( String dn ) throws LdapException
3619    {
3620        delete( new Dn( dn ) );
3621    }
3622
3623
3624    /**
3625     * {@inheritDoc}
3626     */
3627    @Override
3628    public void delete( Dn dn ) throws LdapException
3629    {
3630        DeleteRequest deleteRequest = new DeleteRequestImpl();
3631        deleteRequest.setName( dn );
3632
3633        DeleteResponse deleteResponse = delete( deleteRequest );
3634
3635        processResponse( deleteResponse );
3636    }
3637
3638
3639    /**
3640     * deletes the entry with the given Dn, and all its children
3641     *
3642     * @param dn the target entry's Dn
3643     * @throws LdapException If the Dn is not valid or if the deletion failed
3644     */
3645    public void deleteTree( Dn dn ) throws LdapException
3646    {
3647        if ( isControlSupported( TreeDelete.OID ) )
3648        {
3649            DeleteRequest deleteRequest = new DeleteRequestImpl();
3650            deleteRequest.setName( dn );
3651            deleteRequest.addControl( new TreeDeleteImpl() );
3652            DeleteResponse deleteResponse = delete( deleteRequest );
3653
3654            processResponse( deleteResponse );
3655        }
3656        else
3657        {
3658            String msg = I18n.err( I18n.ERR_04148_SUBTREE_CONTROL_NOT_SUPPORTED );
3659            LOG.error( msg );
3660            throw new LdapException( msg );
3661        }
3662    }
3663
3664
3665    /**
3666     * deletes the entry with the given Dn, and all its children
3667     *
3668     * @param dn the target entry's Dn as a String
3669     * @throws LdapException If the Dn is not valid or if the deletion failed
3670     */
3671    public void deleteTree( String dn ) throws LdapException
3672    {
3673        try
3674        {
3675            String treeDeleteOid = "1.2.840.113556.1.4.805";
3676            Dn newDn = new Dn( dn );
3677
3678            if ( isControlSupported( treeDeleteOid ) )
3679            {
3680                DeleteRequest deleteRequest = new DeleteRequestImpl();
3681                deleteRequest.setName( newDn );
3682                deleteRequest.addControl( new OpaqueControl( treeDeleteOid ) );
3683                DeleteResponse deleteResponse = delete( deleteRequest );
3684
3685                processResponse( deleteResponse );
3686            }
3687            else
3688            {
3689                String msg = I18n.err( I18n.ERR_04148_SUBTREE_CONTROL_NOT_SUPPORTED );
3690                LOG.error( msg );
3691                throw new LdapException( msg );
3692            }
3693        }
3694        catch ( LdapInvalidDnException e )
3695        {
3696            LOG.error( e.getMessage(), e );
3697            throw new LdapException( e.getMessage(), e );
3698        }
3699    }
3700
3701
3702    /**
3703     * {@inheritDoc}
3704     */
3705    @Override
3706    public DeleteResponse delete( DeleteRequest deleteRequest ) throws LdapException
3707    {
3708        if ( deleteRequest == null )
3709        {
3710            String msg = I18n.err( I18n.ERR_04149_CANNOT_PROCESS_NULL_DEL_REQ );
3711
3712            if ( LOG.isDebugEnabled() )
3713            {
3714                LOG.debug( msg );
3715            }
3716            
3717            throw new IllegalArgumentException( msg );
3718        }
3719
3720        DeleteFuture deleteFuture = deleteAsync( deleteRequest );
3721
3722        // Get the result from the future
3723        try
3724        {
3725            // Read the response, waiting for it if not available immediately
3726            // Get the response, blocking
3727            DeleteResponse delResponse = deleteFuture.get( timeout, TimeUnit.MILLISECONDS );
3728
3729            if ( delResponse == null )
3730            {
3731                // We didn't received anything : this is an error
3732                if ( LOG.isErrorEnabled() )
3733                {
3734                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Delete" ) );
3735                }
3736                
3737                throw new LdapException( TIME_OUT_ERROR );
3738            }
3739
3740            if ( delResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3741            {
3742                // Everything is fine, return the response
3743                if ( LOG.isDebugEnabled() )
3744                { 
3745                    LOG.debug( I18n.msg( I18n.MSG_04116_DELETE_SUCCESSFUL, delResponse ) );
3746                }
3747            }
3748            else
3749            {
3750                // We have had an error
3751                if ( LOG.isDebugEnabled() )
3752                { 
3753                    LOG.debug( I18n.msg( I18n.MSG_04115_DELETE_FAILED, delResponse ) );
3754                }
3755            }
3756
3757            return delResponse;
3758        }
3759        catch ( Exception ie )
3760        {
3761            // Catch all other exceptions
3762            LOG.error( NO_RESPONSE_ERROR, ie );
3763
3764            // Send an abandon request
3765            if ( !deleteFuture.isCancelled() )
3766            {
3767                abandon( deleteRequest.getMessageId() );
3768            }
3769
3770            throw new LdapException( NO_RESPONSE_ERROR, ie );
3771        }
3772    }
3773
3774
3775    /**
3776     * {@inheritDoc}
3777     */
3778    @Override
3779    public DeleteFuture deleteAsync( DeleteRequest deleteRequest ) throws LdapException
3780    {
3781        if ( deleteRequest == null )
3782        {
3783            String msg = I18n.err( I18n.ERR_04149_CANNOT_PROCESS_NULL_DEL_REQ );
3784
3785            if ( LOG.isDebugEnabled() )
3786            {
3787                LOG.debug( msg );
3788            }
3789            
3790            throw new IllegalArgumentException( msg );
3791        }
3792
3793        if ( deleteRequest.getName() == null )
3794        {
3795            String msg = I18n.err( I18n.ERR_04150_CANNOT_PROCESS_NULL_DEL_NULL_DN );
3796
3797            if ( LOG.isDebugEnabled() )
3798            {
3799                LOG.debug( msg );
3800            }
3801            
3802            throw new IllegalArgumentException( msg );
3803        }
3804
3805        // try to connect, if we aren't already connected.
3806        connect();
3807
3808        checkSession();
3809
3810        int newId = messageId.incrementAndGet();
3811
3812        deleteRequest.setMessageId( newId );
3813
3814        DeleteFuture deleteFuture = new DeleteFuture( this, newId );
3815        addToFutureMap( newId, deleteFuture );
3816
3817        // Send the request to the server
3818        writeRequest( deleteRequest );
3819
3820        // Ok, done return the future
3821        return deleteFuture;
3822    }
3823
3824
3825    /**
3826     * {@inheritDoc}
3827     */
3828    @Override
3829    public boolean compare( String dn, String attributeName, String value ) throws LdapException
3830    {
3831        return compare( new Dn( dn ), attributeName, value );
3832    }
3833
3834
3835    /**
3836     * {@inheritDoc}
3837     */
3838    @Override
3839    public boolean compare( String dn, String attributeName, byte[] value ) throws LdapException
3840    {
3841        return compare( new Dn( dn ), attributeName, value );
3842    }
3843
3844
3845    /**
3846     * {@inheritDoc}
3847     */
3848    @Override
3849    public boolean compare( String dn, String attributeName, Value value ) throws LdapException
3850    {
3851        return compare( new Dn( dn ), attributeName, value );
3852    }
3853
3854
3855    /**
3856     * {@inheritDoc}
3857     */
3858    @Override
3859    public boolean compare( Dn dn, String attributeName, String value ) throws LdapException
3860    {
3861        CompareRequest compareRequest = new CompareRequestImpl();
3862        compareRequest.setName( dn );
3863        compareRequest.setAttributeId( attributeName );
3864        compareRequest.setAssertionValue( value );
3865
3866        CompareResponse compareResponse = compare( compareRequest );
3867
3868        return processResponse( compareResponse );
3869    }
3870
3871
3872    /**
3873     * {@inheritDoc}
3874     */
3875    @Override
3876    public boolean compare( Dn dn, String attributeName, byte[] value ) throws LdapException
3877    {
3878        CompareRequest compareRequest = new CompareRequestImpl();
3879        compareRequest.setName( dn );
3880        compareRequest.setAttributeId( attributeName );
3881        compareRequest.setAssertionValue( value );
3882
3883        CompareResponse compareResponse = compare( compareRequest );
3884
3885        return processResponse( compareResponse );
3886    }
3887
3888
3889    /**
3890     * {@inheritDoc}
3891     */
3892    @Override
3893    public boolean compare( Dn dn, String attributeName, Value value ) throws LdapException
3894    {
3895        CompareRequest compareRequest = new CompareRequestImpl();
3896        compareRequest.setName( dn );
3897        compareRequest.setAttributeId( attributeName );
3898
3899        if ( value.isHumanReadable() )
3900        {
3901            compareRequest.setAssertionValue( value.getString() );
3902        }
3903        else
3904        {
3905            compareRequest.setAssertionValue( value.getBytes() );
3906        }
3907
3908        CompareResponse compareResponse = compare( compareRequest );
3909
3910        return processResponse( compareResponse );
3911    }
3912
3913
3914    /**
3915     * {@inheritDoc}
3916     */
3917    @Override
3918    public CompareResponse compare( CompareRequest compareRequest ) throws LdapException
3919    {
3920        if ( compareRequest == null )
3921        {
3922            String msg = I18n.err( I18n.ERR_04151_CANNOT_PROCESS_NULL_COMP_REQ );
3923
3924            if ( LOG.isDebugEnabled() )
3925            {
3926                LOG.debug( msg );
3927            }
3928            
3929            throw new IllegalArgumentException( msg );
3930        }
3931
3932        CompareFuture compareFuture = compareAsync( compareRequest );
3933
3934        // Get the result from the future
3935        try
3936        {
3937            // Read the response, waiting for it if not available immediately
3938            // Get the response, blocking
3939            CompareResponse compareResponse = compareFuture.get( timeout, TimeUnit.MILLISECONDS );
3940
3941            if ( compareResponse == null )
3942            {
3943                // We didn't received anything : this is an error
3944                if ( LOG.isErrorEnabled() )
3945                {
3946                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Compare" ) );
3947                }
3948                
3949                throw new LdapException( TIME_OUT_ERROR );
3950            }
3951
3952            if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3953            {
3954                // Everything is fine, return the response
3955                if ( LOG.isDebugEnabled() )
3956                { 
3957                    LOG.debug( I18n.msg( I18n.MSG_04114_COMPARE_SUCCESSFUL, compareResponse ) );
3958                }
3959            }
3960            else
3961            {
3962                // We have had an error
3963                if ( LOG.isDebugEnabled() )
3964                { 
3965                    LOG.debug( I18n.msg( I18n.MSG_04113_COMPARE_FAILED, compareResponse ) );
3966                }
3967            }
3968
3969            return compareResponse;
3970        }
3971        catch ( Exception ie )
3972        {
3973            // Catch all other exceptions
3974            LOG.error( NO_RESPONSE_ERROR, ie );
3975
3976            // Send an abandon request
3977            if ( !compareFuture.isCancelled() )
3978            {
3979                abandon( compareRequest.getMessageId() );
3980            }
3981
3982            throw new LdapException( NO_RESPONSE_ERROR, ie );
3983        }
3984    }
3985
3986
3987    /**
3988     * {@inheritDoc}
3989     */
3990    @Override
3991    public CompareFuture compareAsync( CompareRequest compareRequest ) throws LdapException
3992    {
3993        if ( compareRequest == null )
3994        {
3995            String msg = I18n.err( I18n.ERR_04151_CANNOT_PROCESS_NULL_COMP_REQ );
3996
3997            if ( LOG.isDebugEnabled() )
3998            {
3999                LOG.debug( msg );
4000            }
4001            
4002            throw new IllegalArgumentException( msg );
4003        }
4004
4005        if ( compareRequest.getName() == null )
4006        {
4007            String msg = I18n.err( I18n.ERR_04152_CANNOT_PROCESS_NULL_DN_COMP_REQ );
4008
4009            if ( LOG.isDebugEnabled() )
4010            {
4011                LOG.debug( msg );
4012            }
4013            
4014            throw new IllegalArgumentException( msg );
4015        }
4016
4017        // try to connect, if we aren't already connected.
4018        connect();
4019
4020        checkSession();
4021
4022        int newId = messageId.incrementAndGet();
4023
4024        compareRequest.setMessageId( newId );
4025
4026        CompareFuture compareFuture = new CompareFuture( this, newId );
4027        addToFutureMap( newId, compareFuture );
4028
4029        // Send the request to the server
4030        writeRequest( compareRequest );
4031
4032        // Ok, done return the future
4033        return compareFuture;
4034    }
4035
4036
4037    /**
4038     * {@inheritDoc}
4039     */
4040    @Override
4041    public ExtendedResponse extended( String oid ) throws LdapException
4042    {
4043        return extended( oid, null );
4044    }
4045
4046
4047    /**
4048     * {@inheritDoc}
4049     */
4050    @Override
4051    public ExtendedResponse extended( String oid, byte[] value ) throws LdapException
4052    {
4053        try
4054        {
4055            return extended( Oid.fromString( oid ), value );
4056        }
4057        catch ( DecoderException e )
4058        {
4059            String msg = I18n.err( I18n.ERR_04153_OID_DECODING_FAILURE, oid );
4060            LOG.error( msg );
4061            throw new LdapException( msg, e );
4062        }
4063    }
4064
4065
4066    /**
4067     * {@inheritDoc}
4068     */
4069    @Override
4070    public ExtendedResponse extended( Oid oid ) throws LdapException
4071    {
4072        return extended( oid, null );
4073    }
4074
4075
4076    /**
4077     * {@inheritDoc}
4078     */
4079    @Override
4080    public ExtendedResponse extended( Oid oid, byte[] value ) throws LdapException
4081    {
4082        Map<String, ExtendedOperationFactory> factories = LdapApiServiceFactory.getSingleton().getExtendedRequestFactories();
4083        String oidStr = oid.toString();
4084        
4085        ExtendedOperationFactory factory = factories.get( oidStr );
4086        
4087        if ( factory != null )
4088        {
4089            try
4090            {
4091                if ( value == null )
4092                {
4093                    return extended( factory.newRequest() );
4094                }
4095                else
4096                {
4097                    return extended( factory.newRequest( value ) );
4098                }
4099            }
4100            catch ( DecoderException de )
4101            {
4102                throw new LdapNoSuchObjectException( de.getMessage() );
4103            }
4104        }
4105        else
4106        {
4107            return extended( new OpaqueExtendedRequest( oidStr, value ) );
4108        }
4109    }
4110
4111
4112    /**
4113     * {@inheritDoc}
4114     */
4115    @Override
4116    public ExtendedResponse extended( ExtendedRequest extendedRequest ) throws LdapException
4117    {
4118        if ( extendedRequest == null )
4119        {
4120            String msg = I18n.err( I18n.ERR_04154_CANNOT_PROCESS_NULL_EXT_REQ );
4121
4122            if ( LOG.isDebugEnabled() )
4123            {
4124                LOG.debug( msg );
4125            }
4126            
4127            throw new IllegalArgumentException( msg );
4128        }
4129
4130        ExtendedFuture extendedFuture = extendedAsync( extendedRequest );
4131
4132        // Get the result from the future
4133        try
4134        {
4135            // Read the response, waiting for it if not available immediately
4136            // Get the response, blocking
4137            ExtendedResponse response = ( ExtendedResponse ) extendedFuture
4138                .get( timeout, TimeUnit.MILLISECONDS );
4139
4140            if ( response == null )
4141            {
4142                // We didn't received anything : this is an error
4143                if ( LOG.isErrorEnabled() )
4144                {
4145                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Extended" ) );
4146                }
4147                
4148                throw new LdapException( TIME_OUT_ERROR );
4149            }
4150
4151            if ( response.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
4152            {
4153                // Everything is fine, return the response
4154                if ( LOG.isDebugEnabled() )
4155                { 
4156                    LOG.debug( I18n.msg( I18n.MSG_04118_EXTENDED_SUCCESSFUL, response ) );
4157                }
4158            }
4159            else
4160            {
4161                // We have had an error
4162                if ( LOG.isDebugEnabled() )
4163                { 
4164                    LOG.debug( I18n.msg( I18n.MSG_04117_EXTENDED_FAILED, response ) );
4165                }
4166            }
4167
4168            // Get back the response. It's still an opaque response
4169            if ( Strings.isEmpty( response.getResponseName() ) )
4170            {
4171                response.setResponseName( extendedRequest.getRequestName() );
4172            }
4173
4174            // Decode the payload now
4175            return response;
4176        }
4177        catch ( Exception ie )
4178        {
4179            if ( ie instanceof LdapException )
4180            {
4181                throw ( LdapException ) ie;
4182            }
4183
4184            // Catch all other exceptions
4185            LOG.error( NO_RESPONSE_ERROR, ie );
4186
4187            // Send an abandon request
4188            if ( !extendedFuture.isCancelled() )
4189            {
4190                abandon( extendedRequest.getMessageId() );
4191            }
4192
4193            throw new LdapException( NO_RESPONSE_ERROR, ie );
4194        }
4195    }
4196
4197
4198    /**
4199     * {@inheritDoc}
4200     */
4201    @Override
4202    public ExtendedFuture extendedAsync( ExtendedRequest extendedRequest ) throws LdapException
4203    {
4204        if ( extendedRequest == null )
4205        {
4206            String msg = I18n.err( I18n.ERR_04154_CANNOT_PROCESS_NULL_EXT_REQ );
4207
4208            if ( LOG.isDebugEnabled() )
4209            {
4210                LOG.debug( msg );
4211            }
4212            
4213            throw new IllegalArgumentException( msg );
4214        }
4215
4216        // try to connect, if we aren't already connected.
4217        connect();
4218
4219        checkSession();
4220
4221        int newId = messageId.incrementAndGet();
4222
4223        extendedRequest.setMessageId( newId );
4224        ExtendedFuture extendedFuture = new ExtendedFuture( this, newId );
4225        extendedFuture.setExtendedRequest( extendedRequest );
4226        addToFutureMap( newId, extendedFuture );
4227
4228        // Send the request to the server
4229        writeRequest( extendedRequest );
4230
4231        // Ok, done return the future
4232        return extendedFuture;
4233    }
4234
4235
4236    /**
4237     * {@inheritDoc}
4238     */
4239    @Override
4240    public boolean exists( String dn ) throws LdapException
4241    {
4242        return exists( new Dn( dn ) );
4243    }
4244
4245
4246    /**
4247     * {@inheritDoc}
4248     */
4249    @Override
4250    public boolean exists( Dn dn ) throws LdapException
4251    {
4252        try
4253        {
4254            Entry entry = lookup( dn, SchemaConstants.NO_ATTRIBUTE_ARRAY );
4255
4256            return entry != null;
4257        }
4258        catch ( LdapNoPermissionException lnpe )
4259        {
4260            // Special case to deal with insufficient permissions
4261            return false;
4262        }
4263        catch ( LdapException le )
4264        {
4265            throw le;
4266        }
4267    }
4268
4269
4270    /**
4271     * {@inheritDoc}
4272     */
4273    @Override
4274    public Entry getRootDse() throws LdapException
4275    {
4276        return lookup( Dn.ROOT_DSE, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
4277    }
4278
4279
4280    /**
4281     * {@inheritDoc}
4282     */
4283    @Override
4284    public Entry getRootDse( String... attributes ) throws LdapException
4285    {
4286        return lookup( Dn.ROOT_DSE, attributes );
4287    }
4288
4289
4290    /**
4291     * {@inheritDoc}
4292     */
4293    @Override
4294    public Entry lookup( Dn dn ) throws LdapException
4295    {
4296        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
4297    }
4298
4299
4300    /**
4301     * {@inheritDoc}
4302     */
4303    @Override
4304    public Entry lookup( String dn ) throws LdapException
4305    {
4306        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
4307    }
4308
4309
4310    /**
4311     * {@inheritDoc}
4312     */
4313    @Override
4314    public Entry lookup( Dn dn, String... attributes ) throws LdapException
4315    {
4316        return lookup( dn, null, attributes );
4317    }
4318
4319
4320    /**
4321     * {@inheritDoc}
4322     */
4323    @Override
4324    public Entry lookup( Dn dn, Control[] controls, String... attributes ) throws LdapException
4325    {
4326        Entry entry = null;
4327
4328        try
4329        {
4330            SearchRequest searchRequest = new SearchRequestImpl();
4331
4332            searchRequest.setBase( dn );
4333            searchRequest.setFilter( LdapConstants.OBJECT_CLASS_STAR );
4334            searchRequest.setScope( SearchScope.OBJECT );
4335            searchRequest.addAttributes( attributes );
4336            searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
4337
4338            if ( ( controls != null ) && ( controls.length > 0 ) )
4339            {
4340                searchRequest.addAllControls( controls );
4341            }
4342
4343            try ( Cursor<Response> cursor = search( searchRequest ) )
4344            {
4345                // Read the response
4346                if ( cursor.next() )
4347                {
4348                    // cursor will always hold SearchResultEntry objects cause there is no ManageDsaITControl passed with search request
4349                    entry = ( ( SearchResultEntry ) cursor.get() ).getEntry();
4350                }
4351    
4352                // Pass through the SaerchResultDone, or stop
4353                // if we have other responses
4354                cursor.next();
4355            }
4356        }
4357        catch ( CursorException e )
4358        {
4359            throw new LdapException( e.getMessage(), e );
4360        }
4361        catch ( IOException ioe )
4362        {
4363            throw new LdapException( ioe.getMessage(), ioe );
4364        }
4365
4366        return entry;
4367    }
4368
4369
4370    /**
4371     * {@inheritDoc}
4372     */
4373    @Override
4374    public Entry lookup( String dn, String... attributes ) throws LdapException
4375    {
4376        return lookup( new Dn( dn ), null, attributes );
4377    }
4378
4379
4380    /**
4381     * {@inheritDoc}
4382     */
4383    @Override
4384    public Entry lookup( String dn, Control[] controls, String... attributes ) throws LdapException
4385    {
4386        return lookup( new Dn( dn ), controls, attributes );
4387    }
4388
4389
4390    /**
4391     * {@inheritDoc}
4392     */
4393    @Override
4394    public boolean isControlSupported( String controlOID ) throws LdapException
4395    {
4396        return getSupportedControls().contains( controlOID );
4397    }
4398
4399
4400    /**
4401     * {@inheritDoc}
4402     */
4403    @Override
4404    public List<String> getSupportedControls() throws LdapException
4405    {
4406        if ( supportedControls != null )
4407        {
4408            return supportedControls;
4409        }
4410
4411        if ( rootDse == null )
4412        {
4413            fetchRootDSE();
4414        }
4415
4416        supportedControls = new ArrayList<>();
4417
4418        Attribute attr = rootDse.get( SchemaConstants.SUPPORTED_CONTROL_AT );
4419
4420        if ( attr == null )
4421        {
4422            // Unlikely. Perhaps the server does not respond properly to "+" attribute query
4423            // (such as 389ds server). So let's try again and let's be more explicit.
4424            fetchRootDSE( SchemaConstants.ALL_USER_ATTRIBUTES, 
4425                SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.SUPPORTED_CONTROL_AT );
4426            attr = rootDse.get( SchemaConstants.SUPPORTED_CONTROL_AT );
4427            if ( attr == null )
4428            {
4429                return supportedControls;
4430            }
4431        }
4432        
4433        for ( Value value : attr )
4434        {
4435            supportedControls.add( value.getString() );
4436        }
4437
4438        return supportedControls;
4439    }
4440
4441
4442    /**
4443     * {@inheritDoc}
4444     */
4445    @Override
4446    public void loadSchema() throws LdapException
4447    {
4448        loadSchema( new DefaultSchemaLoader( this ) );
4449    }
4450
4451
4452    /**
4453     * {@inheritDoc}
4454     */
4455    @Override
4456    public void loadSchemaRelaxed() throws LdapException
4457    {
4458        loadSchema( new DefaultSchemaLoader( this, true ) );
4459    }
4460
4461
4462    /**
4463     * loads schema using the specified schema loader
4464     *
4465     * @param loader the {@link SchemaLoader} to be used to load schema
4466     * @throws LdapException If the schema loading failed
4467     */
4468    public void loadSchema( SchemaLoader loader ) throws LdapException
4469    {
4470        try
4471        {
4472            SchemaManager tmp = new DefaultSchemaManager( loader );
4473
4474            tmp.loadAllEnabled();
4475
4476            if ( !tmp.getErrors().isEmpty() && loader.isStrict() )
4477            {
4478                String msg = I18n.err( I18n.ERR_04115_ERROR_LOADING_SCHEMA );
4479                
4480                if ( LOG.isErrorEnabled() )
4481                {
4482                    LOG.error( I18n.err( I18n.ERR_05114_ERROR_MESSAGE, msg, Strings.listToString( tmp.getErrors() ) ) );
4483                }
4484                
4485                throw new LdapException( msg );
4486            }
4487
4488            schemaManager = tmp;
4489
4490            // Change the container's BinaryDetector
4491            ioSession.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR,
4492                new LdapMessageContainer<>( codec,
4493                    new SchemaBinaryAttributeDetector( schemaManager ) ) );
4494
4495        }
4496        catch ( LdapException le )
4497        {
4498            throw le;
4499        }
4500        catch ( Exception e )
4501        {
4502            LOG.error( I18n.err( I18n.ERR_04116_FAIL_LOAD_SCHEMA ), e );
4503            throw new LdapException( e );
4504        }
4505    }
4506
4507
4508    /**
4509     * parses the given schema file present in OpenLDAP schema format
4510     * and adds all the SchemaObjects present in it to the SchemaManager
4511     *
4512     * @param schemaFile the schema file in OpenLDAP schema format
4513     * @throws LdapException in case of any errors while parsing
4514     */
4515    public void addSchema( File schemaFile ) throws LdapException
4516    {
4517        try
4518        {
4519            if ( schemaManager == null )
4520            {
4521                loadSchema();
4522            }
4523            
4524            if ( schemaManager == null )
4525            {
4526                throw new LdapException( I18n.err( I18n.ERR_04116_FAIL_LOAD_SCHEMA ) );
4527            }
4528
4529            OpenLdapSchemaParser olsp = new OpenLdapSchemaParser();
4530            olsp.setQuirksMode( true );
4531            olsp.parse( schemaFile );
4532
4533            Registries registries = schemaManager.getRegistries();
4534
4535            for ( AttributeType atType : olsp.getAttributeTypes() )
4536            {
4537                registries.buildReference( atType );
4538                registries.getAttributeTypeRegistry().register( atType );
4539            }
4540
4541            for ( ObjectClass oc : olsp.getObjectClasses() )
4542            {
4543                registries.buildReference( oc );
4544                registries.getObjectClassRegistry().register( oc );
4545            }
4546
4547            if ( LOG.isInfoEnabled() )
4548            {
4549                LOG.info( I18n.msg( I18n.MSG_04167_SCHEMA_LOADED_SUCCESSFULLY, schemaFile.getAbsolutePath() ) );
4550            }
4551        }
4552        catch ( Exception e )
4553        {
4554            LOG.error( I18n.err( I18n.ERR_04117_FAIL_LOAD_SCHEMA_FILE, schemaFile.getAbsolutePath() ) );
4555            throw new LdapException( e );
4556        }
4557    }
4558
4559
4560    /**
4561     * @see #addSchema(File)
4562     * @param schemaFileName The schema file name to add
4563     * @throws LdapException If the schema addition failed
4564     */
4565    public void addSchema( String schemaFileName ) throws LdapException
4566    {
4567        addSchema( new File( schemaFileName ) );
4568    }
4569
4570
4571    /**
4572     * {@inheritDoc}
4573     */
4574    @Override
4575    public LdapApiService getCodecService()
4576    {
4577        return codec;
4578    }
4579
4580
4581    /**
4582     * {@inheritDoc}
4583     */
4584    @Override
4585    public SchemaManager getSchemaManager()
4586    {
4587        return schemaManager;
4588    }
4589
4590
4591    /**
4592     * fetches the rootDSE from the server
4593     * 
4594     * @param explicitAttributes The list of requested attributes
4595     * @throws LdapException If we weren't bale to fetch the RootDSE
4596     */
4597    private void fetchRootDSE( String... explicitAttributes ) throws LdapException
4598    {
4599        EntryCursor cursor = null;
4600
4601        String[] attributes = explicitAttributes;
4602        if ( attributes.length == 0 )
4603        {
4604            attributes = new String[]
4605                { SchemaConstants.ALL_USER_ATTRIBUTES, SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES };
4606        }
4607        
4608        try
4609        {
4610            cursor = search( "", LdapConstants.OBJECT_CLASS_STAR, SearchScope.OBJECT, attributes );
4611            if ( cursor.next() )
4612            {
4613                rootDse = cursor.get();
4614            }
4615            else
4616            {
4617                throw new LdapException( I18n.err( I18n.ERR_04155_ROOT_DSE_SEARCH_FAILED ) );
4618            }
4619        }
4620        catch ( Exception e )
4621        {
4622            String msg = I18n.err( I18n.ERR_04156_FAILED_FETCHING_ROOT_DSE );
4623            LOG.error( msg );
4624            throw new LdapException( msg, e );
4625        }
4626        finally
4627        {
4628            if ( cursor != null )
4629            {
4630                try
4631                {
4632                    cursor.close();
4633                }
4634                catch ( Exception e )
4635                {
4636                    LOG.error( I18n.err( I18n.ERR_04114_CURSOR_CLOSE_FAIL ), e );
4637                }
4638            }
4639        }
4640    }
4641
4642
4643    /**
4644     * gives the configuration information of the connection
4645     *
4646     * @return the configuration of the connection
4647     */
4648    @Override
4649    public LdapConnectionConfig getConfig()
4650    {
4651        return config;
4652    }
4653
4654
4655    /**
4656     * removes the Objects associated with the given message ID
4657     * from future and response queue maps
4658     *
4659     * @param msgId id of the message
4660     */
4661    private void removeFromFutureMaps( int msgId )
4662    {
4663        getFromFutureMap( msgId );
4664    }
4665
4666
4667    /**
4668     * clears the async listener, responseQueue and future mapppings to the corresponding request IDs
4669     */
4670    private void clearMaps()
4671    {
4672        futureMap.clear();
4673    }
4674
4675
4676    /**
4677     * {@inheritDoc}
4678     */
4679    @Override
4680    public boolean isRequestCompleted( int messageId )
4681    {
4682        ResponseFuture<?> responseFuture = futureMap.get( messageId );
4683        
4684        return responseFuture == null;
4685    }
4686
4687
4688    /**
4689     * {@inheritDoc}
4690     */
4691    @Override
4692    public boolean doesFutureExistFor( int messageId )
4693    {
4694        ResponseFuture<?> responseFuture = futureMap.get( messageId );
4695        return responseFuture != null;
4696    }
4697
4698
4699    /**
4700     * Adds the connection closed event listener.
4701     *
4702     * @param ccListener the connection closed listener
4703     */
4704    public void addConnectionClosedEventListener( ConnectionClosedEventListener ccListener )
4705    {
4706        if ( conCloseListeners == null )
4707        {
4708            conCloseListeners = new ArrayList<>();
4709        }
4710
4711        conCloseListeners.add( ccListener );
4712    }
4713    
4714    
4715    /**
4716     * {@inheritDoc}
4717     */
4718    @Override
4719    public void inputClosed( IoSession session ) throws Exception 
4720    {
4721        session.closeNow();
4722    }
4723
4724
4725    /**
4726     * This method is called when a new session is created. We will store some
4727     * informations that the session will need to process incoming requests.
4728     * 
4729     * @param session the newly created session
4730     */
4731    @Override
4732    public void sessionCreated( IoSession session ) throws Exception
4733    {
4734        // Last, store the message container
4735        LdapMessageContainer<Message> ldapMessageContainer =
4736            new LdapMessageContainer<>(
4737                codec, config.getBinaryAttributeDetector() );
4738
4739        session.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR, ldapMessageContainer );
4740    }
4741
4742
4743    /**
4744     * {@inheritDoc}
4745     */
4746    @Override
4747    public void sessionClosed( IoSession session ) throws Exception
4748    {
4749        authenticated.set( false );
4750        
4751        // Close all the Future for this session
4752        for ( ResponseFuture<? extends Response> responseFuture : futureMap.values() )
4753        {
4754            responseFuture.cancel();
4755        }
4756
4757        // clear the mappings
4758        clearMaps();
4759
4760        // Last, not least, reset the MessageId value
4761        messageId.set( 0 );
4762
4763        connectorMutex.lock();
4764
4765        try
4766        {
4767            if ( connector != null )
4768            {
4769                connector.dispose();
4770                connector = null;
4771            }
4772        }
4773        finally
4774        {
4775            connectorMutex.unlock();
4776        }
4777
4778        if ( conCloseListeners != null )
4779        {
4780            if ( LOG.isDebugEnabled() )
4781            {
4782                LOG.debug( I18n.msg( I18n.MSG_04136_NOTIFYING_CLOSE_LISTENERS ) );
4783            }
4784
4785            for ( ConnectionClosedEventListener listener : conCloseListeners )
4786            {
4787                listener.connectionClosed();
4788            }
4789        }
4790        
4791        connectionCloseFuture.complete( 0 );
4792    }
4793
4794
4795    /**
4796     * Sends the StartTLS extended request to server and adds a security layer
4797     * upon receiving a response with successful result. Note that we will use
4798     * the default LDAP connection.
4799     *
4800     * @throws LdapException If the StartTLS operation failed
4801     */
4802    public void startTls() throws LdapException
4803    {
4804        try
4805        {
4806            if ( config.isUseSsl() )
4807            {
4808                throw new LdapException( I18n.err( I18n.ERR_04157_CANNOT_USE_TLS_WITH_SSL_FLAG ) );
4809            }
4810
4811            // try to connect, if we aren't already connected.
4812            connect();
4813
4814            checkSession();
4815            
4816            if ( ioSession.isSecured() )
4817            {
4818                if ( LOG.isDebugEnabled() )
4819                { 
4820                    LOG.debug( I18n.msg( I18n.MSG_04121_LDAP_ALREADY_USING_START_TLS ) );
4821                }
4822                
4823                return;
4824            }
4825
4826            ExtendedResponse resp = extended( new StartTlsRequestImpl() );
4827            LdapResult result = resp.getLdapResult();
4828
4829            if ( result.getResultCode() == ResultCodeEnum.SUCCESS )
4830            {
4831                addSslFilter();
4832            }
4833            else
4834            {
4835                throw new LdapOperationException( result.getResultCode(), result.getDiagnosticMessage() );
4836            }
4837        }
4838        catch ( LdapException e )
4839        {
4840            throw e;
4841        }
4842        catch ( Exception e )
4843        {
4844            throw new LdapException( e );
4845        }
4846    }
4847
4848
4849    /**
4850     * Adds {@link SslFilter} to the IOConnector or IOSession's filter chain
4851     * 
4852     * @throws LdapException If the SSL filter addition failed
4853     */
4854    private void addSslFilter() throws LdapException
4855    {
4856        try
4857        {
4858            SSLContext sslContext = SSLContext.getInstance( config.getSslProtocol() );
4859            
4860            sslContext.init( config.getKeyManagers(), config.getTrustManagers(), config.getSecureRandom() );
4861
4862            SslFilter sslFilter = new SslFilter( sslContext );
4863            sslFilter.setUseClientMode( true );
4864
4865            // Configure the enabled cipher lists
4866            String[] enabledCipherSuite = config.getEnabledCipherSuites();
4867
4868            if ( ( enabledCipherSuite != null ) && ( enabledCipherSuite.length != 0 ) )
4869            {
4870                sslFilter.setEnabledCipherSuites( enabledCipherSuite );
4871            }
4872
4873            // Be sure we disable SSLV3
4874            String[] enabledProtocols = config.getEnabledProtocols();
4875
4876            if ( ( enabledProtocols != null ) && ( enabledProtocols.length != 0 ) )
4877            {
4878                sslFilter.setEnabledProtocols( enabledProtocols );
4879            }
4880            else
4881            {
4882                // Default to TLS
4883                sslFilter.setEnabledProtocols( new String[]
4884                    { "TLSv1", "TLSv1.1", "TLSv1.2" } );
4885            }
4886
4887            // for LDAPS/TLS
4888            handshakeFuture = new HandshakeFuture();
4889            
4890            if ( ( ioSession == null ) || !isConnected() )
4891            {
4892                connector.getFilterChain().addFirst( SSL_FILTER_KEY, sslFilter );
4893            }
4894            else
4895            // for StartTLS
4896            {
4897                ioSession.getFilterChain().addFirst( SSL_FILTER_KEY, sslFilter );
4898                
4899                boolean isSecured = handshakeFuture.get( timeout, TimeUnit.MILLISECONDS );
4900                
4901                if ( !isSecured )
4902                {
4903                    Throwable cause = ( Throwable ) ioSession.getAttribute( EXCEPTION_KEY );
4904                    throw new LdapTlsHandshakeException( I18n.err( I18n.ERR_04120_TLS_HANDSHAKE_ERROR ), cause );
4905                }
4906            }
4907        }
4908        catch ( Exception e )
4909        {
4910            if ( e instanceof LdapException )
4911            {
4912                throw ( LdapException ) e;
4913            }
4914
4915            String msg = I18n.err( I18n.ERR_04122_SSL_CONTEXT_INIT_FAILURE );
4916            LOG.error( msg, e );
4917            throw new LdapException( msg, e );
4918        }
4919    }
4920
4921
4922    /**
4923     * Process the SASL Bind. It's a dialog with the server, we will send a first BindRequest, receive
4924     * a response and the, if this response is a challenge, continue by sending a new BindRequest with
4925     * the requested informations.
4926     *
4927     * @param saslRequest The SASL request object containing all the needed parameters
4928     * @return A {@link BindResponse} containing the result
4929     * @throws LdapException if some error occurred
4930     */
4931    public BindFuture bindSasl( SaslRequest saslRequest ) throws LdapException
4932    {
4933        // First switch to anonymous state
4934        authenticated.set( false );
4935
4936        // try to connect, if we aren't already connected.
4937        connect();
4938
4939        // If the session has not been establish, or is closed, we get out immediately
4940        checkSession();
4941
4942        BindRequest bindRequest = createBindRequest( ( String ) null, null,
4943            saslRequest.getSaslMechanism(), saslRequest.getControls() );
4944
4945        // Update the messageId
4946        int newId = messageId.incrementAndGet();
4947        bindRequest.setMessageId( newId );
4948
4949        if ( LOG.isDebugEnabled() )
4950        {
4951            LOG.debug( I18n.msg( I18n.MSG_04104_SENDING_REQUEST, bindRequest ) );
4952        }
4953
4954        // Create a future for this Bind operation
4955        BindFuture bindFuture = new BindFuture( this, newId );
4956
4957        // Store it in the future Map
4958        addToFutureMap( newId, bindFuture );
4959
4960        try
4961        {
4962            BindResponse bindResponse;
4963            byte[] response;
4964            ResultCodeEnum result;
4965
4966            // Creating a map for SASL properties
4967            Map<String, Object> properties = new HashMap<>();
4968
4969            // Quality of Protection SASL property
4970            if ( saslRequest.getQualityOfProtection() != null )
4971            {
4972                properties.put( Sasl.QOP, saslRequest.getQualityOfProtection().getValue() );
4973            }
4974
4975            // Security Strength SASL property
4976            if ( saslRequest.getSecurityStrength() != null )
4977            {
4978                properties.put( Sasl.STRENGTH, saslRequest.getSecurityStrength().getValue() );
4979            }
4980
4981            // Mutual Authentication SASL property
4982            if ( saslRequest.isMutualAuthentication() )
4983            {
4984                properties.put( Sasl.SERVER_AUTH, "true" );
4985            }
4986
4987            // Creating a SASL Client
4988            SaslClient sc = Sasl.createSaslClient(
4989                new String[]
4990                    { bindRequest.getSaslMechanism() },
4991                saslRequest.getAuthorizationId(),
4992                "ldap",
4993                config.getLdapHost(),
4994                properties,
4995                new SaslCallbackHandler( saslRequest ) );
4996
4997            // If the SaslClient wasn't created, that means we can't create the SASL client
4998            // for the requested mechanism. We then produce an Exception
4999            if ( sc == null )
5000            {
5001                String message = I18n.err( I18n.ERR_04158_CANNOT_FIND_SASL_FACTORY_FOR_MECH, bindRequest.getSaslMechanism() );
5002                LOG.error( message );
5003                throw new LdapException( message );
5004            }
5005
5006            // Corner case : the SASL mech might send an initial challenge, and we have to
5007            // deal with it immediately.
5008            if ( sc.hasInitialResponse() )
5009            {
5010                byte[] challengeResponse = sc.evaluateChallenge( Strings.EMPTY_BYTES );
5011
5012                // Stores the challenge's response, and send it to the server
5013                bindRequest.setCredentials( challengeResponse );
5014                writeRequest( bindRequest );
5015
5016                // Get the server's response, blocking
5017                bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
5018
5019                if ( bindResponse == null )
5020                {
5021                    // We didn't received anything : this is an error
5022                    if ( LOG.isErrorEnabled() )
5023                    { 
5024                        LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
5025                    }
5026                    
5027                    throw new LdapException( TIME_OUT_ERROR );
5028                }
5029
5030                result = bindResponse.getLdapResult().getResultCode();
5031            }
5032            else
5033            {
5034                // Copy the bindRequest without setting the credentials
5035                BindRequest bindRequestCopy = new BindRequestImpl();
5036                bindRequestCopy.setMessageId( newId );
5037
5038                bindRequestCopy.setName( bindRequest.getName() );
5039                bindRequestCopy.setSaslMechanism( bindRequest.getSaslMechanism() );
5040                bindRequestCopy.setSimple( bindRequest.isSimple() );
5041                bindRequestCopy.setVersion3( bindRequest.getVersion3() );
5042                bindRequestCopy.addAllControls( bindRequest.getControls().values().toArray( new Control[0] ) );
5043
5044                writeRequest( bindRequestCopy );
5045
5046                bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
5047
5048                if ( bindResponse == null )
5049                {
5050                    // We didn't received anything : this is an error
5051                    if ( LOG.isErrorEnabled() )
5052                    {
5053                        LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
5054                    }
5055                    
5056                    throw new LdapException( TIME_OUT_ERROR );
5057                }
5058
5059                result = bindResponse.getLdapResult().getResultCode();
5060            }
5061
5062            while ( !sc.isComplete()
5063                && ( ( result == ResultCodeEnum.SASL_BIND_IN_PROGRESS ) || ( result == ResultCodeEnum.SUCCESS ) ) )
5064            {
5065                response = sc.evaluateChallenge( bindResponse.getServerSaslCreds() );
5066
5067                if ( result == ResultCodeEnum.SUCCESS )
5068                {
5069                    if ( response != null )
5070                    {
5071                        throw new LdapException( I18n.err( I18n.ERR_04159_PROTOCOL_ERROR ) );
5072                    }
5073                }
5074                else
5075                {
5076                    newId = messageId.incrementAndGet();
5077                    bindRequest.setMessageId( newId );
5078                    bindRequest.setCredentials( response );
5079
5080                    addToFutureMap( newId, bindFuture );
5081
5082                    writeRequest( bindRequest );
5083
5084                    bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
5085
5086                    if ( bindResponse == null )
5087                    {
5088                        // We didn't received anything : this is an error
5089                        if ( LOG.isErrorEnabled() )
5090                        {
5091                            LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
5092                        }
5093                        
5094                        throw new LdapException( TIME_OUT_ERROR );
5095                    }
5096
5097                    result = bindResponse.getLdapResult().getResultCode();
5098                }
5099            }
5100
5101            bindFuture.set( bindResponse );
5102
5103            return bindFuture;
5104        }
5105        catch ( LdapException e )
5106        {
5107            throw e;
5108        }
5109        catch ( Exception e )
5110        {
5111            LOG.error( e.getMessage() );
5112            throw new LdapException( e );
5113        }
5114    }
5115
5116
5117    /**
5118     * A reusable code block to be used in various bind methods
5119     * 
5120     * @param request The request to send
5121     * @throws LdapException If the request was ot properly sent
5122     */
5123    private void writeRequest( Request request ) throws LdapException
5124    {
5125        // Send the request to the server
5126        WriteFuture writeFuture = ioSession.write( request );
5127
5128        long localTimeout = timeout;
5129
5130        while ( localTimeout > 0 )
5131        {
5132            // Wait only 100 ms
5133            boolean done = writeFuture.awaitUninterruptibly( 100 );
5134
5135            if ( done )
5136            {
5137                return;
5138            }
5139
5140            // Wait for the message to be sent to the server
5141            if ( !ioSession.isConnected() )
5142            {
5143                // We didn't received anything : this is an error
5144                if ( LOG.isErrorEnabled() )
5145                {
5146                    LOG.error( I18n.err( I18n.ERR_04118_SOMETHING_WRONG_HAPPENED ) );
5147                }
5148
5149                Exception exception = ( Exception ) ioSession.removeAttribute( EXCEPTION_KEY );
5150
5151                if ( exception instanceof LdapException )
5152                {
5153                    throw ( LdapException ) exception;
5154                }
5155                else if ( exception != null )
5156                {
5157                    throw new InvalidConnectionException( exception.getMessage(), exception );
5158                }
5159
5160                throw new InvalidConnectionException( I18n.err( I18n.ERR_04160_SESSION_HAS_BEEN_CLOSED ) );
5161            }
5162
5163            localTimeout -= 100;
5164        }
5165
5166        if ( LOG.isErrorEnabled() )
5167        {
5168            LOG.error( I18n.err( I18n.ERR_04119_TIMEOUT ) );
5169        }
5170        
5171        throw new LdapException( TIME_OUT_ERROR );
5172    }
5173
5174
5175    /**
5176     * method to write the kerberos config in the standard MIT kerberos format
5177     *
5178     * This is required cause the JGSS api is not able to recognize the port value set
5179     * in the system property java.security.krb5.kdc this issue makes it impossible
5180     * to set a kdc running non standard ports (other than 88)
5181     *
5182     * e.g localhost:6088
5183     *
5184     * <pre>
5185     * [libdefaults]
5186     *     default_realm = EXAMPLE.COM
5187     *
5188     * [realms]
5189     *     EXAMPLE.COM = {
5190     *         kdc = localhost:6088
5191     *     }
5192     * </pre>
5193     *
5194     * @param realmName The realm name
5195     * @param kdcHost The Kerberos server host
5196     * @param kdcPort The Kerberos server port
5197     * @return the full path of the config file
5198     * @throws IOException If the config file cannot be created
5199     */
5200    private String createKrb5ConfFile( String realmName, String kdcHost, int kdcPort ) throws IOException
5201    {
5202        StringBuilder sb = new StringBuilder();
5203
5204        sb.append( "[libdefaults]" )
5205            .append( "\n\t" );
5206        sb.append( "default_realm = " )
5207            .append( realmName )
5208            .append( "\n" );
5209
5210        sb.append( "[realms]" )
5211            .append( "\n\t" );
5212
5213        sb.append( realmName )
5214            .append( " = {" )
5215            .append( "\n\t\t" );
5216        sb.append( "kdc = " )
5217            .append( kdcHost )
5218            .append( ":" )
5219            .append( kdcPort )
5220            .append( "\n\t}\n" );
5221
5222        File krb5Conf = Files.createTempFile( "client-api-krb5", ".conf" ).toFile();
5223        krb5Conf.deleteOnExit();
5224
5225        try ( Writer writer = new OutputStreamWriter( Files.newOutputStream( Paths.get( krb5Conf.getPath() ) ), 
5226            Charset.defaultCharset() ) )
5227        {
5228            writer.write( sb.toString() );
5229        }
5230
5231        String krb5ConfPath = krb5Conf.getAbsolutePath();
5232
5233        if ( LOG.isDebugEnabled() )
5234        {
5235            LOG.debug( I18n.msg( I18n.MSG_04135_KRB5_FILE_CREATED, krb5ConfPath ) );
5236        }
5237
5238        return krb5ConfPath;
5239    }
5240
5241
5242    /**
5243     * {@inheritDoc}
5244     */
5245    @Override
5246    public BinaryAttributeDetector getBinaryAttributeDetector()
5247    {
5248        if ( config != null )
5249        {
5250            return config.getBinaryAttributeDetector();
5251        }
5252        else
5253        {
5254            return null;
5255        }
5256    }
5257
5258
5259    /**
5260     * {@inheritDoc}
5261     */
5262    @Override
5263    public void setBinaryAttributeDetector( BinaryAttributeDetector binaryAttributeDetector )
5264    {
5265        if ( config != null )
5266        {
5267            config.setBinaryAttributeDetector( binaryAttributeDetector );
5268        }
5269    }
5270
5271
5272    /**
5273     * {@inheritDoc}
5274     */
5275    @Override
5276    public void setSchemaManager( SchemaManager schemaManager )
5277    {
5278        this.schemaManager = schemaManager;
5279    }
5280
5281
5282    /**
5283     * @return the socketSessionConfig
5284     */
5285    public SocketSessionConfig getSocketSessionConfig()
5286    {
5287        return socketSessionConfig;
5288    }
5289
5290
5291    /**
5292     * @param socketSessionConfig the socketSessionConfig to set
5293     */
5294    public void setSocketSessionConfig( SocketSessionConfig socketSessionConfig )
5295    {
5296        this.socketSessionConfig = socketSessionConfig;
5297    }
5298    
5299    
5300    /**
5301     * {@inheritDoc}
5302     */
5303    @Override
5304    public void event( IoSession session, FilterEvent event ) throws Exception 
5305    {
5306        // Check if it's a SSLevent 
5307        if ( ( event instanceof SslEvent ) && ( ( SslEvent ) event == SslEvent.SECURED ) )
5308        {
5309            handshakeFuture.secured();
5310        }
5311    }
5312}