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