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