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