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