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