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