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