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