001    /*
002     * Copyright 2007-2014 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2014 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.util.Collection;
026    import java.util.HashMap;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Timer;
030    import java.util.concurrent.atomic.AtomicBoolean;
031    import java.util.concurrent.atomic.AtomicLong;
032    import java.util.concurrent.atomic.AtomicReference;
033    import java.util.logging.Level;
034    import javax.net.SocketFactory;
035    import javax.net.ssl.SSLSocketFactory;
036    import javax.security.sasl.SaslClient;
037    
038    import com.unboundid.asn1.ASN1OctetString;
039    import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
040    import com.unboundid.ldap.protocol.LDAPMessage;
041    import com.unboundid.ldap.protocol.LDAPResponse;
042    import com.unboundid.ldap.protocol.UnbindRequestProtocolOp;
043    import com.unboundid.ldap.sdk.schema.Schema;
044    import com.unboundid.ldif.LDIFException;
045    import com.unboundid.util.DebugType;
046    import com.unboundid.util.SynchronizedSocketFactory;
047    import com.unboundid.util.SynchronizedSSLSocketFactory;
048    import com.unboundid.util.ThreadSafety;
049    import com.unboundid.util.ThreadSafetyLevel;
050    import com.unboundid.util.WeakHashSet;
051    
052    import static com.unboundid.ldap.sdk.LDAPMessages.*;
053    import static com.unboundid.util.Debug.*;
054    import static com.unboundid.util.StaticUtils.*;
055    import static com.unboundid.util.Validator.*;
056    
057    
058    
059    /**
060     * This class provides a facility for interacting with an LDAPv3 directory
061     * server.  It provides a means of establishing a connection to the server,
062     * sending requests, and reading responses.  See
063     * <A HREF="http://www.ietf.org/rfc/rfc4511.txt">RFC 4511</A> for the LDAPv3
064     * protocol specification and more information about the types of operations
065     * defined in LDAP.
066     * <BR><BR>
067     * <H2>Creating, Establishing, and Authenticating Connections</H2>
068     * An LDAP connection can be established either at the time that the object is
069     * created or as a separate step.  Similarly, authentication can be performed on
070     * the connection at the time it is created, at the time it is established, or
071     * as a separate process.  For example:
072     * <BR><BR>
073     * <PRE>
074     *   // Create a new, unestablished connection.  Then connect and perform a
075     *   // simple bind as separate operations.
076     *   LDAPConnection c = new LDAPConnection();
077     *   c.connect(address, port);
078     *   BindResult bindResult = c.bind(bindDN, password);
079     *
080     *   // Create a new connection that is established at creation time, and then
081     *   // authenticate separately using simple authentication.
082     *   LDAPConnection c = new LDAPConnection(address, port);
083     *   BindResult bindResult = c.bind(bindDN, password);
084     *
085     *   // Create a new connection that is established and bound using simple
086     *   // authentication all in one step.
087     *   LDAPConnection c = new LDAPConnection(address, port, bindDN, password);
088     * </PRE>
089     * <BR><BR>
090     * When authentication is performed at the time that the connection is
091     * established, it is only possible to perform a simple bind and it is not
092     * possible to include controls in the bind request, nor is it possible to
093     * receive response controls if the bind was successful.  Therefore, it is
094     * recommended that authentication be performed as a separate step if the server
095     * may return response controls even in the event of a successful authentication
096     * (e.g., a control that may indicate that the user's password will soon
097     * expire).  See the {@link BindRequest} class for more information about
098     * authentication in the UnboundID LDAP SDK for Java.
099     * <BR><BR>
100     * By default, connections will use standard unencrypted network sockets.
101     * However, it may be desirable to create connections that use SSL/TLS to
102     * encrypt communication.  This can be done by specifying a
103     * {@link javax.net.SocketFactory} that should be used to create the socket to
104     * use to communicate with the directory server.  The
105     * {@link javax.net.ssl.SSLSocketFactory#getDefault} method or the
106     * {@link javax.net.ssl.SSLContext#getSocketFactory} method may be used to
107     * obtain a socket factory for performing SSL communication.  See the
108     * <A HREF=
109     * "http://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html">
110     * JSSE Reference Guide</A> for more information on using these classes.
111     * Alternately, you may use the {@link com.unboundid.util.ssl.SSLUtil} class to
112     * simplify the process.
113     * <BR><BR>
114     * Whenever the connection is no longer needed, it may be terminated using the
115     * {@link LDAPConnection#close} method.
116     * <BR><BR>
117     * <H2>Processing LDAP Operations</H2>
118     * This class provides a number of methods for processing the different types of
119     * operations.  The types of operations that can be processed include:
120     * <UL>
121     *   <LI>Abandon -- This may be used to request that the server stop processing
122     *      on an operation that has been invoked asynchronously.</LI>
123     *   <LI>Add -- This may be used to add a new entry to the directory
124     *       server.  See the {@link AddRequest} class for more information about
125     *       processing add operations.</LI>
126     *   <LI>Bind -- This may be used to authenticate to the directory server.  See
127     *       the {@link BindRequest} class for more information about processing
128     *       bind operations.</LI>
129     *   <LI>Compare -- This may be used to determine whether a specified entry has
130     *       a given attribute value.  See the {@link CompareRequest} class for more
131     *       information about processing compare operations.</LI>
132     *   <LI>Delete -- This may be used to remove an entry from the directory
133     *       server.  See the {@link DeleteRequest} class for more information about
134     *       processing delete operations.</LI>
135     *   <LI>Extended -- This may be used to process an operation which is not
136     *       part of the core LDAP protocol but is a custom extension supported by
137     *       the directory server.  See the {@link ExtendedRequest} class for more
138     *       information about processing extended operations.</LI>
139     *   <LI>Modify -- This may be used to alter an entry in the directory
140     *       server.  See the {@link ModifyRequest} class for more information about
141     *       processing modify operations.</LI>
142     *   <LI>Modify DN -- This may be used to rename an entry or subtree and/or move
143     *       that entry or subtree below a new parent in the directory server.  See
144     *       the {@link ModifyDNRequest} class for more information about processing
145     *       modify DN operations.</LI>
146     *   <LI>Search -- This may be used to retrieve a set of entries in the server
147     *       that match a given set of criteria.  See the {@link SearchRequest}
148     *       class for more information about processing search operations.</LI>
149     * </UL>
150     * <BR><BR>
151     * Most of the methods in this class used to process operations operate in a
152     * synchronous manner.  In these cases, the SDK will send a request to the
153     * server and wait for a response to arrive before returning to the caller.  In
154     * these cases, the value returned will include the contents of that response,
155     * including the result code, diagnostic message, matched DN, referral URLs, and
156     * any controls that may have been included.  However, it also possible to
157     * process operations asynchronously, in which case the SDK will return control
158     * back to the caller after the request has been sent to the server but before
159     * the response has been received.  In this case, the SDK will return an
160     * {@link AsyncRequestID} object which may be used to later abandon or cancel
161     * that operation if necessary, and will notify the client when the response
162     * arrives via a listener interface.
163     * <BR><BR>
164     * This class is mostly threadsafe.  It is possible to process multiple
165     * concurrent operations over the same connection as long as the methods being
166     * invoked will not change the state of the connection in a way that might
167     * impact other operations in progress in unexpected ways.  In particular, the
168     * following should not be attempted while any other operations may be in
169     * progress on this connection:
170     * <UL>
171     *   <LI>
172     *     Using one of the {@code connect} methods to re-establish the connection.
173     *   </LI>
174     *   <LI>
175     *     Using one of the {@code close} methods to terminate the connection.
176     *   </LI>
177     *   <LI>
178     *     Using one of the {@code bind} methods to attempt to authenticate the
179     *     connection (unless you are certain that the bind will not impact the
180     *     identity of the associated connection, for example by including the
181     *     retain identity request control in the bind request if using the
182     *     Commercial Edition of the LDAP SDK in conjunction with an UnboundID
183     *     Directory Server).
184     *   </LI>
185     *   <LI>
186     *     Attempting to make a change to the way that the underlying communication
187     *     is processed (e.g., by using the StartTLS extended operation to convert
188     *     an insecure connection into a secure one).
189     *   </LI>
190     * </UL>
191     */
192    @ThreadSafety(level=ThreadSafetyLevel.MOSTLY_THREADSAFE)
193    public final class LDAPConnection
194           implements LDAPInterface, ReferralConnector
195    {
196      /**
197       * The counter that will be used when assigning connection IDs to connections.
198       */
199      private static final AtomicLong NEXT_CONNECTION_ID = new AtomicLong(0L);
200    
201    
202    
203      /**
204       * The default socket factory that will be used if no alternate factory is
205       * provided.
206       */
207      private static final SocketFactory DEFAULT_SOCKET_FACTORY =
208                                              SocketFactory.getDefault();
209    
210    
211    
212      /**
213       * A set of weak references to schema objects that can be shared across
214       * connections if they are identical.
215       */
216      private static final WeakHashSet<Schema> SCHEMA_SET =
217           new WeakHashSet<Schema>();
218    
219    
220    
221      // The connection pool with which this connection is associated, if
222      // applicable.
223      private AbstractConnectionPool connectionPool;
224    
225      // Indicates whether to perform a reconnect before the next write.
226      private final AtomicBoolean needsReconnect;
227    
228      // The last successful bind request processed on this connection.
229      private BindRequest lastBindRequest;
230    
231      // Indicates whether a request has been made to close this connection.
232      private volatile boolean closeRequested;
233    
234      // Indicates whether an unbind request has been sent over this connection.
235      private volatile boolean unbindRequestSent;
236    
237      // The disconnect information for this connection.
238      private final AtomicReference<DisconnectInfo> disconnectInfo;
239    
240      // The port of the server to which a connection should be re-established.
241      private int reconnectPort = -1;
242    
243      // The connection internals used to actually perform the network
244      // communication.
245      private volatile LDAPConnectionInternals connectionInternals;
246    
247      // The set of connection options for this connection.
248      private LDAPConnectionOptions connectionOptions;
249    
250      // The set of statistics for this connection.
251      private final LDAPConnectionStatistics connectionStatistics;
252    
253      // The unique identifier assigned to this connection when it was created.  It
254      // will not change over the life of the connection, even if the connection is
255      // closed and re-established (or even re-established to a different server).
256      private final long connectionID;
257    
258      // The time of the last rebind attempt.
259      private long lastReconnectTime;
260    
261      // The most recent time that an LDAP message was sent or received on this
262      // connection.
263      private volatile long lastCommunicationTime;
264    
265      // A map in which arbitrary attachments may be stored or managed.
266      private Map<String,Object> attachments;
267    
268      // The referral connector that will be used to establish connections to remote
269      // servers when following a referral.
270      private volatile ReferralConnector referralConnector;
271    
272      // The cached schema read from the server.
273      private volatile Schema cachedSchema;
274    
275      // The socket factory used for the last connection attempt.
276      private SocketFactory lastUsedSocketFactory;
277    
278      // The socket factory used to create sockets for subsequent connection
279      // attempts.
280      private volatile SocketFactory socketFactory;
281    
282      // A stack trace of the thread that last established this connection.
283      private StackTraceElement[] connectStackTrace;
284    
285      // The user-friendly name assigned to this connection.
286      private String connectionName;
287    
288      // The user-friendly name assigned to the connection pool with which this
289      // connection is associated.
290      private String connectionPoolName;
291    
292      // A string representation of the host and port to which the last connection
293      // attempt (whether successful or not, and whether it is still established)
294      // was made.
295      private String hostPort;
296    
297      // The address of the server to which a connection should be re-established.
298      private String reconnectAddress;
299    
300      // A timer that may be used to enforce timeouts for asynchronous operations.
301      private Timer timer;
302    
303    
304    
305      /**
306       * Creates a new LDAP connection using the default socket factory and default
307       * set of connection options.  No actual network connection will be
308       * established.
309       */
310      public LDAPConnection()
311      {
312        this(null, null);
313      }
314    
315    
316    
317      /**
318       * Creates a new LDAP connection using the default socket factory and provided
319       * set of connection options.  No actual network connection will be
320       * established.
321       *
322       * @param  connectionOptions  The set of connection options to use for this
323       *                            connection.  If it is {@code null}, then a
324       *                            default set of options will be used.
325       */
326      public LDAPConnection(final LDAPConnectionOptions connectionOptions)
327      {
328        this(null, connectionOptions);
329      }
330    
331    
332    
333      /**
334       * Creates a new LDAP connection using the specified socket factory.  No
335       * actual network connection will be established.
336       *
337       * @param  socketFactory  The socket factory to use when establishing
338       *                        connections.  If it is {@code null}, then a default
339       *                        socket factory will be used.
340       */
341      public LDAPConnection(final SocketFactory socketFactory)
342      {
343        this(socketFactory, null);
344      }
345    
346    
347    
348      /**
349       * Creates a new LDAP connection using the specified socket factory.  No
350       * actual network connection will be established.
351       *
352       * @param  socketFactory      The socket factory to use when establishing
353       *                            connections.  If it is {@code null}, then a
354       *                            default socket factory will be used.
355       * @param  connectionOptions  The set of connection options to use for this
356       *                            connection.  If it is {@code null}, then a
357       *                            default set of options will be used.
358       */
359      public LDAPConnection(final SocketFactory socketFactory,
360                            final LDAPConnectionOptions connectionOptions)
361      {
362        needsReconnect = new AtomicBoolean(false);
363        disconnectInfo = new AtomicReference<DisconnectInfo>();
364        lastCommunicationTime = -1L;
365    
366        connectionID = NEXT_CONNECTION_ID.getAndIncrement();
367    
368        if (connectionOptions == null)
369        {
370          this.connectionOptions = new LDAPConnectionOptions();
371        }
372        else
373        {
374          this.connectionOptions = connectionOptions.duplicate();
375        }
376    
377        final SocketFactory f;
378        if (socketFactory == null)
379        {
380          f = DEFAULT_SOCKET_FACTORY;
381        }
382        else
383        {
384          f = socketFactory;
385        }
386    
387        if (this.connectionOptions.allowConcurrentSocketFactoryUse())
388        {
389          this.socketFactory = f;
390        }
391        else
392        {
393          if (f instanceof SSLSocketFactory)
394          {
395            this.socketFactory =
396                 new SynchronizedSSLSocketFactory((SSLSocketFactory) f);
397          }
398          else
399          {
400            this.socketFactory = new SynchronizedSocketFactory(f);
401          }
402        }
403    
404        attachments          = null;
405        connectionStatistics = new LDAPConnectionStatistics();
406        connectionName       = null;
407        connectionPoolName   = null;
408        cachedSchema         = null;
409        timer                = null;
410    
411        referralConnector = this.connectionOptions.getReferralConnector();
412        if (referralConnector == null)
413        {
414          referralConnector = this;
415        }
416      }
417    
418    
419    
420      /**
421       * Creates a new, unauthenticated LDAP connection that is established to the
422       * specified server.
423       *
424       * @param  host  The address of the server to which the connection should be
425       *               established.  It must not be {@code null}.
426       * @param  port  The port number of the server to which the connection should
427       *               be established.  It should be a value between 1 and 65535,
428       *               inclusive.
429       *
430       * @throws  LDAPException  If a problem occurs while attempting to connect to
431       *                         the specified server.
432       */
433      public LDAPConnection(final String host, final int port)
434             throws LDAPException
435      {
436        this(null, null, host, port);
437      }
438    
439    
440    
441      /**
442       * Creates a new, unauthenticated LDAP connection that is established to the
443       * specified server.
444       *
445       * @param  connectionOptions  The set of connection options to use for this
446       *                            connection.  If it is {@code null}, then a
447       *                            default set of options will be used.
448       * @param  host               The address of the server to which the
449       *                            connection should be established.  It must not
450       *                            be {@code null}.
451       * @param  port               The port number of the server to which the
452       *                            connection should be established.  It should be
453       *                            a value between 1 and 65535, inclusive.
454       *
455       * @throws  LDAPException  If a problem occurs while attempting to connect to
456       *                         the specified server.
457       */
458      public LDAPConnection(final LDAPConnectionOptions connectionOptions,
459                            final String host, final int port)
460             throws LDAPException
461      {
462        this(null, connectionOptions, host, port);
463      }
464    
465    
466    
467      /**
468       * Creates a new, unauthenticated LDAP connection that is established to the
469       * specified server.
470       *
471       * @param  socketFactory  The socket factory to use when establishing
472       *                        connections.  If it is {@code null}, then a default
473       *                        socket factory will be used.
474       * @param  host           The address of the server to which the connection
475       *                        should be established.  It must not be {@code null}.
476       * @param  port           The port number of the server to which the
477       *                        connection should be established.  It should be a
478       *                        value between 1 and 65535, inclusive.
479       *
480       * @throws  LDAPException  If a problem occurs while attempting to connect to
481       *                         the specified server.
482       */
483      public LDAPConnection(final SocketFactory socketFactory, final String host,
484                            final int port)
485             throws LDAPException
486      {
487        this(socketFactory, null, host, port);
488      }
489    
490    
491    
492      /**
493       * Creates a new, unauthenticated LDAP connection that is established to the
494       * specified server.
495       *
496       * @param  socketFactory      The socket factory to use when establishing
497       *                            connections.  If it is {@code null}, then a
498       *                            default socket factory will be used.
499       * @param  connectionOptions  The set of connection options to use for this
500       *                            connection.  If it is {@code null}, then a
501       *                            default set of options will be used.
502       * @param  host               The address of the server to which the
503       *                            connection should be established.  It must not
504       *                            be {@code null}.
505       * @param  port               The port number of the server to which the
506       *                            connection should be established.  It should be
507       *                            a value between 1 and 65535, inclusive.
508       *
509       * @throws  LDAPException  If a problem occurs while attempting to connect to
510       *                         the specified server.
511       */
512      public LDAPConnection(final SocketFactory socketFactory,
513                            final LDAPConnectionOptions connectionOptions,
514                            final String host, final int port)
515             throws LDAPException
516      {
517        this(socketFactory, connectionOptions);
518    
519        connect(host, port);
520      }
521    
522    
523    
524      /**
525       * Creates a new LDAP connection that is established to the specified server
526       * and is authenticated as the specified user (via LDAP simple
527       * authentication).
528       *
529       * @param  host          The address of the server to which the connection
530       *                       should be established.  It must not be {@code null}.
531       * @param  port          The port number of the server to which the
532       *                       connection should be established.  It should be a
533       *                       value between 1 and 65535, inclusive.
534       * @param  bindDN        The DN to use to authenticate to the directory
535       *                       server.
536       * @param  bindPassword  The password to use to authenticate to the directory
537       *                       server.
538       *
539       * @throws  LDAPException  If a problem occurs while attempting to connect to
540       *                         the specified server.
541       */
542      public LDAPConnection(final String host, final int port, final String bindDN,
543                            final String bindPassword)
544             throws LDAPException
545      {
546        this(null, null, host, port, bindDN, bindPassword);
547      }
548    
549    
550    
551      /**
552       * Creates a new LDAP connection that is established to the specified server
553       * and is authenticated as the specified user (via LDAP simple
554       * authentication).
555       *
556       * @param  connectionOptions  The set of connection options to use for this
557       *                            connection.  If it is {@code null}, then a
558       *                            default set of options will be used.
559       * @param  host               The address of the server to which the
560       *                            connection should be established.  It must not
561       *                            be {@code null}.
562       * @param  port               The port number of the server to which the
563       *                            connection should be established.  It should be
564       *                            a value between 1 and 65535, inclusive.
565       * @param  bindDN             The DN to use to authenticate to the directory
566       *                            server.
567       * @param  bindPassword       The password to use to authenticate to the
568       *                            directory server.
569       *
570       * @throws  LDAPException  If a problem occurs while attempting to connect to
571       *                         the specified server.
572       */
573      public LDAPConnection(final LDAPConnectionOptions connectionOptions,
574                            final String host, final int port, final String bindDN,
575                            final String bindPassword)
576             throws LDAPException
577      {
578        this(null, connectionOptions, host, port, bindDN, bindPassword);
579      }
580    
581    
582    
583      /**
584       * Creates a new LDAP connection that is established to the specified server
585       * and is authenticated as the specified user (via LDAP simple
586       * authentication).
587       *
588       * @param  socketFactory  The socket factory to use when establishing
589       *                        connections.  If it is {@code null}, then a default
590       *                        socket factory will be used.
591       * @param  host           The address of the server to which the connection
592       *                        should be established.  It must not be {@code null}.
593       * @param  port           The port number of the server to which the
594       *                        connection should be established.  It should be a
595       *                        value between 1 and 65535, inclusive.
596       * @param  bindDN         The DN to use to authenticate to the directory
597       *                        server.
598       * @param  bindPassword   The password to use to authenticate to the directory
599       *                        server.
600       *
601       * @throws  LDAPException  If a problem occurs while attempting to connect to
602       *                         the specified server.
603       */
604      public LDAPConnection(final SocketFactory socketFactory, final String host,
605                            final int port, final String bindDN,
606                            final String bindPassword)
607             throws LDAPException
608      {
609        this(socketFactory, null, host, port, bindDN, bindPassword);
610      }
611    
612    
613    
614      /**
615       * Creates a new LDAP connection that is established to the specified server
616       * and is authenticated as the specified user (via LDAP simple
617       * authentication).
618       *
619       * @param  socketFactory      The socket factory to use when establishing
620       *                            connections.  If it is {@code null}, then a
621       *                            default socket factory will be used.
622       * @param  connectionOptions  The set of connection options to use for this
623       *                            connection.  If it is {@code null}, then a
624       *                            default set of options will be used.
625       * @param  host               The address of the server to which the
626       *                            connection should be established.  It must not
627       *                            be {@code null}.
628       * @param  port               The port number of the server to which the
629       *                            connection should be established.  It should be
630       *                            a value between 1 and 65535, inclusive.
631       * @param  bindDN             The DN to use to authenticate to the directory
632       *                            server.
633       * @param  bindPassword       The password to use to authenticate to the
634       *                            directory server.
635       *
636       * @throws  LDAPException  If a problem occurs while attempting to connect to
637       *                         the specified server.
638       */
639      public LDAPConnection(final SocketFactory socketFactory,
640                            final LDAPConnectionOptions connectionOptions,
641                            final String host, final int port, final String bindDN,
642                            final String bindPassword)
643             throws LDAPException
644      {
645        this(socketFactory, connectionOptions, host, port);
646    
647        try
648        {
649          bind(new SimpleBindRequest(bindDN, bindPassword));
650        }
651        catch (LDAPException le)
652        {
653          debugException(le);
654          setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
655          close();
656          throw le;
657        }
658      }
659    
660    
661    
662      /**
663       * Establishes an unauthenticated connection to the directory server using the
664       * provided information.  If the connection is already established, then it
665       * will be closed and re-established.
666       * <BR><BR>
667       * If this method is invoked while any operations are in progress on this
668       * connection, then the directory server may or may not abort processing for
669       * those operations, depending on the type of operation and how far along the
670       * server has already gotten while processing that operation.  It is
671       * recommended that all active operations be abandoned, canceled, or allowed
672       * to complete before attempting to re-establish an active connection.
673       *
674       * @param  host  The address of the server to which the connection should be
675       *               established.  It must not be {@code null}.
676       * @param  port  The port number of the server to which the connection should
677       *               be established.  It should be a value between 1 and 65535,
678       *               inclusive.
679       *
680       * @throws  LDAPException  If an error occurs while attempting to establish
681       *                         the connection.
682       */
683      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
684      public void connect(final String host, final int port)
685             throws LDAPException
686      {
687        connect(host, port, connectionOptions.getConnectTimeoutMillis());
688      }
689    
690    
691    
692      /**
693       * Establishes an unauthenticated connection to the directory server using the
694       * provided information.  If the connection is already established, then it
695       * will be closed and re-established.
696       * <BR><BR>
697       * If this method is invoked while any operations are in progress on this
698       * connection, then the directory server may or may not abort processing for
699       * those operations, depending on the type of operation and how far along the
700       * server has already gotten while processing that operation.  It is
701       * recommended that all active operations be abandoned, canceled, or allowed
702       * to complete before attempting to re-establish an active connection.
703       *
704       * @param  host     The address of the server to which the connection should
705       *                  be established.  It must not be {@code null}.
706       * @param  port     The port number of the server to which the connection
707       *                  should be established.  It should be a value between 1 and
708       *                  65535, inclusive.
709       * @param  timeout  The maximum length of time in milliseconds to wait for the
710       *                  connection to be established before failing, or zero to
711       *                  indicate that no timeout should be enforced (although if
712       *                  the attempt stalls long enough, then the underlying
713       *                  operating system may cause it to timeout).
714       *
715       * @throws  LDAPException  If an error occurs while attempting to establish
716       *                         the connection.
717       */
718      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
719      public void connect(final String host, final int port, final int timeout)
720             throws LDAPException
721      {
722        ensureNotNull(host, port);
723    
724        needsReconnect.set(false);
725        hostPort = host + ':' + port;
726        lastCommunicationTime = -1L;
727    
728        if (isConnected())
729        {
730          setDisconnectInfo(DisconnectType.RECONNECT, null, null);
731          close();
732        }
733    
734        lastUsedSocketFactory = socketFactory;
735        reconnectAddress      = host;
736        reconnectPort         = port;
737        cachedSchema          = null;
738        unbindRequestSent     = false;
739    
740        disconnectInfo.set(null);
741    
742        try
743        {
744          connectionStatistics.incrementNumConnects();
745          connectionInternals = new LDAPConnectionInternals(this, connectionOptions,
746               lastUsedSocketFactory, host, port, timeout);
747          connectionInternals.startConnectionReader();
748          lastCommunicationTime = System.currentTimeMillis();
749        }
750        catch (Exception e)
751        {
752          debugException(e);
753          setDisconnectInfo(DisconnectType.LOCAL_ERROR, null, e);
754          connectionInternals = null;
755          throw new LDAPException(ResultCode.CONNECT_ERROR,
756               ERR_CONN_CONNECT_ERROR.get(getHostPort(), getExceptionMessage(e)),
757               e);
758        }
759    
760        if (connectionOptions.useSchema())
761        {
762          try
763          {
764            cachedSchema = getCachedSchema(this);
765          }
766          catch (Exception e)
767          {
768            debugException(e);
769          }
770        }
771      }
772    
773    
774    
775      /**
776       * Attempts to re-establish a connection to the server and re-authenticate if
777       * appropriate.
778       *
779       * @throws  LDAPException  If a problem occurs while attempting to re-connect
780       *                         or re-authenticate.
781       */
782      public void reconnect()
783             throws LDAPException
784      {
785        needsReconnect.set(false);
786        if ((System.currentTimeMillis() - lastReconnectTime) < 1000L)
787        {
788          // If the last reconnect attempt was less than 1 second ago, then abort.
789          throw new LDAPException(ResultCode.SERVER_DOWN,
790                                  ERR_CONN_MULTIPLE_FAILURES.get());
791        }
792    
793        BindRequest bindRequest = null;
794        if (lastBindRequest != null)
795        {
796          bindRequest = lastBindRequest.getRebindRequest(reconnectAddress,
797                                                         reconnectPort);
798          if (bindRequest == null)
799          {
800            throw new LDAPException(ResultCode.SERVER_DOWN,
801                 ERR_CONN_CANNOT_REAUTHENTICATE.get(getHostPort()));
802          }
803        }
804    
805        setDisconnectInfo(DisconnectType.RECONNECT, null, null);
806        terminate(null);
807    
808        try
809        {
810          Thread.sleep(10);
811        } catch (final Exception e) {}
812    
813        connect(reconnectAddress, reconnectPort);
814    
815        if (bindRequest != null)
816        {
817          try
818          {
819            bind(bindRequest);
820          }
821          catch (LDAPException le)
822          {
823            debugException(le);
824            setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
825            terminate(null);
826    
827            throw le;
828          }
829        }
830    
831        lastReconnectTime = System.currentTimeMillis();
832      }
833    
834    
835    
836      /**
837       * Sets a flag indicating that the connection should be re-established before
838       * sending the next request.
839       */
840      void setNeedsReconnect()
841      {
842        needsReconnect.set(true);
843      }
844    
845    
846    
847      /**
848       * Indicates whether this connection is currently established.
849       *
850       * @return  {@code true} if this connection is currently established, or
851       *          {@code false} if it is not.
852       */
853      public boolean isConnected()
854      {
855        final LDAPConnectionInternals internals = connectionInternals;
856    
857        if (internals == null)
858        {
859          return false;
860        }
861    
862        if (! internals.isConnected())
863        {
864          setClosed();
865          return false;
866        }
867    
868        return (! needsReconnect.get());
869      }
870    
871    
872    
873      /**
874       * Converts this clear-text connection to one that encrypts all communication
875       * using Transport Layer Security.  This method is intended for use as a
876       * helper for processing in the course of the StartTLS extended operation and
877       * should not be used for other purposes.
878       *
879       * @param  sslSocketFactory  The SSL socket factory to use to convert an
880       *                           insecure connection into a secure connection.  It
881       *                           must not be {@code null}.
882       *
883       * @throws  LDAPException  If a problem occurs while converting this
884       *                         connection to use TLS.
885       */
886      void convertToTLS(final SSLSocketFactory sslSocketFactory)
887           throws LDAPException
888      {
889        final LDAPConnectionInternals internals = connectionInternals;
890        if (internals == null)
891        {
892          throw new LDAPException(ResultCode.SERVER_DOWN,
893                                  ERR_CONN_NOT_ESTABLISHED.get());
894        }
895        else
896        {
897          internals.convertToTLS(sslSocketFactory);
898        }
899      }
900    
901    
902    
903      /**
904       * Converts this clear-text connection to one that uses SASL integrity and/or
905       * confidentiality.
906       *
907       * @param  saslClient  The SASL client that will be used to secure the
908       *                     communication.
909       *
910       * @throws  LDAPException  If a problem occurs while attempting to convert the
911       *                         connection to use SASL QoP.
912       */
913      void applySASLQoP(final SaslClient saslClient)
914           throws LDAPException
915      {
916        final LDAPConnectionInternals internals = connectionInternals;
917        if (internals == null)
918        {
919          throw new LDAPException(ResultCode.SERVER_DOWN,
920               ERR_CONN_NOT_ESTABLISHED.get());
921        }
922        else
923        {
924          internals.applySASLQoP(saslClient);
925        }
926      }
927    
928    
929    
930      /**
931       * Retrieves the set of connection options for this connection.  Changes to
932       * the object that is returned will directly impact this connection.
933       *
934       * @return  The set of connection options for this connection.
935       */
936      public LDAPConnectionOptions getConnectionOptions()
937      {
938        return connectionOptions;
939      }
940    
941    
942    
943      /**
944       * Specifies the set of connection options for this connection.  Some changes
945       * may not take effect for operations already in progress, and some changes
946       * may not take effect for a connection that is already established.
947       *
948       * @param  connectionOptions  The set of connection options for this
949       *                            connection.  It may be {@code null} if a default
950       *                            set of options is to be used.
951       */
952      public void setConnectionOptions(
953                       final LDAPConnectionOptions connectionOptions)
954      {
955        if (connectionOptions == null)
956        {
957          this.connectionOptions = new LDAPConnectionOptions();
958        }
959        else
960        {
961          final LDAPConnectionOptions newOptions = connectionOptions.duplicate();
962          if (debugEnabled(DebugType.LDAP) && newOptions.useSynchronousMode() &&
963              (! connectionOptions.useSynchronousMode()) && isConnected())
964          {
965            debug(Level.WARNING, DebugType.LDAP,
966                  "A call to LDAPConnection.setConnectionOptions() with " +
967                  "useSynchronousMode=true will have no effect for this " +
968                  "connection because it is already established.  The " +
969                  "useSynchronousMode option must be set before the connection " +
970                  "is established to have any effect.");
971          }
972    
973          this.connectionOptions = newOptions;
974        }
975    
976        final ReferralConnector rc = this.connectionOptions.getReferralConnector();
977        if (rc == null)
978        {
979          referralConnector = this;
980        }
981        else
982        {
983          referralConnector = rc;
984        }
985      }
986    
987    
988    
989      /**
990       * Retrieves the socket factory that was used when creating the socket for the
991       * last connection attempt (whether successful or unsuccessful) for this LDAP
992       * connection.
993       *
994       * @return  The socket factory that was used when creating the socket for the
995       *          last connection attempt for this LDAP connection, or {@code null}
996       *          if no attempt has yet been made to establish this connection.
997       */
998      public SocketFactory getLastUsedSocketFactory()
999      {
1000        return lastUsedSocketFactory;
1001      }
1002    
1003    
1004    
1005      /**
1006       * Retrieves the socket factory to use to create the socket for subsequent
1007       * connection attempts.  This may or may not be the socket factory that was
1008       * used to create the current established connection.
1009       *
1010       * @return  The socket factory to use to create the socket for subsequent
1011       *          connection attempts.
1012       */
1013      public SocketFactory getSocketFactory()
1014      {
1015        return socketFactory;
1016      }
1017    
1018    
1019    
1020      /**
1021       * Specifies the socket factory to use to create the socket for subsequent
1022       * connection attempts.  This will not impact any established connection.
1023       *
1024       * @param  socketFactory  The socket factory to use to create the socket for
1025       *                        subsequent connection attempts.
1026       */
1027      public void setSocketFactory(final SocketFactory socketFactory)
1028      {
1029        if (socketFactory == null)
1030        {
1031          this.socketFactory = DEFAULT_SOCKET_FACTORY;
1032        }
1033        else
1034        {
1035          this.socketFactory = socketFactory;
1036        }
1037      }
1038    
1039    
1040    
1041      /**
1042       * Retrieves a value that uniquely identifies this connection within the JVM
1043       * Each {@code LDAPConnection} object will be assigned a different connection
1044       * ID, and that connection ID will not change over the life of the object,
1045       * even if the connection is closed and re-established (whether re-established
1046       * to the same server or a different server).
1047       *
1048       * @return  A value that uniquely identifies this connection within the JVM.
1049       */
1050      public long getConnectionID()
1051      {
1052        return connectionID;
1053      }
1054    
1055    
1056    
1057      /**
1058       * Retrieves the user-friendly name that has been assigned to this connection.
1059       *
1060       * @return  The user-friendly name that has been assigned to this connection,
1061       *          or {@code null} if none has been assigned.
1062       */
1063      public String getConnectionName()
1064      {
1065        return connectionName;
1066      }
1067    
1068    
1069    
1070      /**
1071       * Specifies the user-friendly name that should be used for this connection.
1072       * This name may be used in debugging to help identify the purpose of this
1073       * connection.  This will have no effect for connections which are part of a
1074       * connection pool.
1075       *
1076       * @param  connectionName  The user-friendly name that should be used for this
1077       *                         connection.
1078       */
1079      public void setConnectionName(final String connectionName)
1080      {
1081        if (connectionPool == null)
1082        {
1083          this.connectionName = connectionName;
1084          if (connectionInternals != null)
1085          {
1086            final LDAPConnectionReader reader =
1087                 connectionInternals.getConnectionReader();
1088            reader.updateThreadName();
1089          }
1090        }
1091      }
1092    
1093    
1094    
1095      /**
1096       * Retrieves the connection pool with which this connection is associated, if
1097       * any.
1098       *
1099       * @return  The connection pool with which this connection is associated, or
1100       *          {@code null} if it is not associated with any connection pool.
1101       */
1102      public AbstractConnectionPool getConnectionPool()
1103      {
1104        return connectionPool;
1105      }
1106    
1107    
1108    
1109      /**
1110       * Retrieves the user-friendly name that has been assigned to the connection
1111       * pool with which this connection is associated.
1112       *
1113       * @return  The user-friendly name that has been assigned to the connection
1114       *          pool with which this connection is associated, or {@code null} if
1115       *          none has been assigned or this connection is not associated with a
1116       *          connection pool.
1117       */
1118      public String getConnectionPoolName()
1119      {
1120        return connectionPoolName;
1121      }
1122    
1123    
1124    
1125      /**
1126       * Specifies the user-friendly name that should be used for the connection
1127       * pool with which this connection is associated.
1128       *
1129       * @param  connectionPoolName  The user-friendly name that should be used for
1130       *                             the connection pool with which this connection
1131       *                             is associated.
1132       */
1133      void setConnectionPoolName(final String connectionPoolName)
1134      {
1135        this.connectionPoolName = connectionPoolName;
1136        if (connectionInternals != null)
1137        {
1138          final LDAPConnectionReader reader =
1139               connectionInternals.getConnectionReader();
1140          reader.updateThreadName();
1141        }
1142      }
1143    
1144    
1145    
1146      /**
1147       * Retrieves a string representation of the host and port for the server to
1148       * to which the last connection attempt was made.  It does not matter whether
1149       * the connection attempt was successful, nor does it matter whether it is
1150       * still established.  This is intended for internal use in error messages.
1151       *
1152       * @return  A string representation of the host and port for the server to
1153       *          which the last connection attempt was made, or an empty string if
1154       *          no connection attempt has yet been made on this connection.
1155       */
1156      String getHostPort()
1157      {
1158        if (hostPort == null)
1159        {
1160          return "";
1161        }
1162        else
1163        {
1164          return hostPort;
1165        }
1166      }
1167    
1168    
1169    
1170      /**
1171       * Retrieves the address of the directory server to which this connection is
1172       * currently established.
1173       *
1174       * @return  The address of the directory server to which this connection is
1175       *          currently established, or {@code null} if the connection is not
1176       *          established.
1177       */
1178      public String getConnectedAddress()
1179      {
1180        final LDAPConnectionInternals internals = connectionInternals;
1181        if (internals == null)
1182        {
1183          return null;
1184        }
1185        else
1186        {
1187          return internals.getHost();
1188        }
1189      }
1190    
1191    
1192    
1193      /**
1194       * Retrieves the port of the directory server to which this connection is
1195       * currently established.
1196       *
1197       * @return  The port of the directory server to which this connection is
1198       *          currently established, or -1 if the connection is not established.
1199       */
1200      public int getConnectedPort()
1201      {
1202        final LDAPConnectionInternals internals = connectionInternals;
1203        if (internals == null)
1204        {
1205          return -1;
1206        }
1207        else
1208        {
1209          return internals.getPort();
1210        }
1211      }
1212    
1213    
1214    
1215      /**
1216       * Retrieves a stack trace of the thread that last attempted to establish this
1217       * connection.  Note that this will only be available if an attempt has been
1218       * made to establish this connection and the
1219       * {@link LDAPConnectionOptions#captureConnectStackTrace()} method for the
1220       * associated connection options returns {@code true}.
1221       *
1222       * @return  A stack trace of the thread that last attempted to establish this
1223       *          connection, or {@code null} connect stack traces are not enabled,
1224       *          or if no attempt has been made to establish this connection.
1225       */
1226      public StackTraceElement[] getConnectStackTrace()
1227      {
1228        return connectStackTrace;
1229      }
1230    
1231    
1232    
1233      /**
1234       * Provides a stack trace for the thread that last attempted to establish this
1235       * connection.
1236       *
1237       * @param  connectStackTrace  A stack trace for the thread that last attempted
1238       *                            to establish this connection.
1239       */
1240      void setConnectStackTrace(final StackTraceElement[] connectStackTrace)
1241      {
1242        this.connectStackTrace = connectStackTrace;
1243      }
1244    
1245    
1246    
1247      /**
1248       * Unbinds from the server and closes the connection.
1249       * <BR><BR>
1250       * If this method is invoked while any operations are in progress on this
1251       * connection, then the directory server may or may not abort processing for
1252       * those operations, depending on the type of operation and how far along the
1253       * server has already gotten while processing that operation.  It is
1254       * recommended that all active operations be abandoned, canceled, or allowed
1255       * to complete before attempting to close an active connection.
1256       */
1257      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1258      public void close()
1259      {
1260        closeRequested = true;
1261        setDisconnectInfo(DisconnectType.UNBIND, null, null);
1262    
1263        if (connectionPool == null)
1264        {
1265          terminate(null);
1266        }
1267        else
1268        {
1269          connectionPool.releaseDefunctConnection(this);
1270        }
1271      }
1272    
1273    
1274    
1275      /**
1276       * Unbinds from the server and closes the connection, optionally including
1277       * the provided set of controls in the unbind request.
1278       * <BR><BR>
1279       * If this method is invoked while any operations are in progress on this
1280       * connection, then the directory server may or may not abort processing for
1281       * those operations, depending on the type of operation and how far along the
1282       * server has already gotten while processing that operation.  It is
1283       * recommended that all active operations be abandoned, canceled, or allowed
1284       * to complete before attempting to close an active connection.
1285       *
1286       * @param  controls  The set of controls to include in the unbind request.  It
1287       *                   may be {@code null} if there are not to be any controls
1288       *                   sent in the unbind request.
1289       */
1290      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1291      public void close(final Control[] controls)
1292      {
1293        closeRequested = true;
1294        setDisconnectInfo(DisconnectType.UNBIND, null, null);
1295    
1296        if (connectionPool == null)
1297        {
1298          terminate(controls);
1299        }
1300        else
1301        {
1302          connectionPool.releaseDefunctConnection(this);
1303        }
1304      }
1305    
1306    
1307    
1308      /**
1309       * Unbinds from the server and closes the connection, optionally including the
1310       * provided set of controls in the unbind request.  This method is only
1311       * intended for internal use, since it does not make any attempt to release
1312       * the connection back to its associated connection pool, if there is one.
1313       *
1314       * @param  controls  The set of controls to include in the unbind request.  It
1315       *                   may be {@code null} if there are not to be any controls
1316       *                   sent in the unbind request.
1317       */
1318      void terminate(final Control[] controls)
1319      {
1320        if (isConnected() && (! unbindRequestSent))
1321        {
1322          try
1323          {
1324            unbindRequestSent = true;
1325            setDisconnectInfo(DisconnectType.UNBIND, null, null);
1326            if (debugEnabled(DebugType.LDAP))
1327            {
1328              debug(Level.INFO, DebugType.LDAP, "Sending LDAP unbind request.");
1329            }
1330    
1331            connectionStatistics.incrementNumUnbindRequests();
1332            sendMessage(new LDAPMessage(nextMessageID(),
1333                 new UnbindRequestProtocolOp(), controls));
1334          }
1335          catch (Exception e)
1336          {
1337            debugException(e);
1338          }
1339        }
1340    
1341        setClosed();
1342      }
1343    
1344    
1345    
1346      /**
1347       * Indicates whether a request has been made to close this connection.
1348       *
1349       * @return  {@code true} if a request has been made to close this connection,
1350       *          or {@code false} if not.
1351       */
1352      boolean closeRequested()
1353      {
1354        return closeRequested;
1355      }
1356    
1357    
1358    
1359      /**
1360       * Indicates whether an unbind request has been sent over this connection.
1361       *
1362       * @return  {@code true} if an unbind request has been sent over this
1363       *          connection, or {@code false} if not.
1364       */
1365      boolean unbindRequestSent()
1366      {
1367        return unbindRequestSent;
1368      }
1369    
1370    
1371    
1372      /**
1373       * Indicates that this LDAP connection is part of the specified
1374       * connection pool.
1375       *
1376       * @param  connectionPool  The connection pool with which this LDAP connection
1377       *                         is associated.
1378       */
1379      void setConnectionPool(final AbstractConnectionPool connectionPool)
1380      {
1381        this.connectionPool = connectionPool;
1382      }
1383    
1384    
1385    
1386      /**
1387       * Retrieves the directory server root DSE, which provides information about
1388       * the directory server, including the capabilities that it provides and the
1389       * type of data that it is configured to handle.
1390       *
1391       * @return  The directory server root DSE, or {@code null} if it is not
1392       *          available.
1393       *
1394       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1395       *                         the server root DSE.
1396       */
1397      public RootDSE getRootDSE()
1398             throws LDAPException
1399      {
1400        return RootDSE.getRootDSE(this);
1401      }
1402    
1403    
1404    
1405      /**
1406       * Retrieves the directory server schema definitions, using the subschema
1407       * subentry DN contained in the server's root DSE.  For directory servers
1408       * containing a single schema, this should be sufficient for all purposes.
1409       * For servers with multiple schemas, it may be necessary to specify the DN
1410       * of the target entry for which to obtain the associated schema.
1411       *
1412       * @return  The directory server schema definitions, or {@code null} if the
1413       *          schema information could not be retrieved (e.g, the client does
1414       *          not have permission to read the server schema).
1415       *
1416       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1417       *                         the server schema.
1418       */
1419      public Schema getSchema()
1420             throws LDAPException
1421      {
1422        return Schema.getSchema(this, "");
1423      }
1424    
1425    
1426    
1427      /**
1428       * Retrieves the directory server schema definitions that govern the specified
1429       * entry.  The subschemaSubentry attribute will be retrieved from the target
1430       * entry, and then the appropriate schema definitions will be loaded from the
1431       * entry referenced by that attribute.  This may be necessary to ensure
1432       * correct behavior in servers that support multiple schemas.
1433       *
1434       * @param  entryDN  The DN of the entry for which to retrieve the associated
1435       *                  schema definitions.  It may be {@code null} or an empty
1436       *                  string if the subschemaSubentry attribute should be
1437       *                  retrieved from the server's root DSE.
1438       *
1439       * @return  The directory server schema definitions, or {@code null} if the
1440       *          schema information could not be retrieved (e.g, the client does
1441       *          not have permission to read the server schema).
1442       *
1443       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1444       *                         the server schema.
1445       */
1446      public Schema getSchema(final String entryDN)
1447             throws LDAPException
1448      {
1449        return Schema.getSchema(this, entryDN);
1450      }
1451    
1452    
1453    
1454      /**
1455       * Retrieves the entry with the specified DN.  All user attributes will be
1456       * requested in the entry to return.
1457       *
1458       * @param  dn  The DN of the entry to retrieve.  It must not be {@code null}.
1459       *
1460       * @return  The requested entry, or {@code null} if the target entry does not
1461       *          exist or no entry was returned (e.g., if the authenticated user
1462       *          does not have permission to read the target entry).
1463       *
1464       * @throws  LDAPException  If a problem occurs while sending the request or
1465       *                         reading the response.
1466       */
1467      public SearchResultEntry getEntry(final String dn)
1468             throws LDAPException
1469      {
1470        return getEntry(dn, (String[]) null);
1471      }
1472    
1473    
1474    
1475      /**
1476       * Retrieves the entry with the specified DN.
1477       *
1478       * @param  dn          The DN of the entry to retrieve.  It must not be
1479       *                     {@code null}.
1480       * @param  attributes  The set of attributes to request for the target entry.
1481       *                     If it is {@code null}, then all user attributes will be
1482       *                     requested.
1483       *
1484       * @return  The requested entry, or {@code null} if the target entry does not
1485       *          exist or no entry was returned (e.g., if the authenticated user
1486       *          does not have permission to read the target entry).
1487       *
1488       * @throws  LDAPException  If a problem occurs while sending the request or
1489       *                         reading the response.
1490       */
1491      public SearchResultEntry getEntry(final String dn, final String... attributes)
1492             throws LDAPException
1493      {
1494        final Filter filter = Filter.createPresenceFilter("objectClass");
1495    
1496        final SearchResult result;
1497        try
1498        {
1499          final SearchRequest searchRequest =
1500               new SearchRequest(dn, SearchScope.BASE, DereferencePolicy.NEVER, 1,
1501                                 0, false, filter, attributes);
1502          result = search(searchRequest);
1503        }
1504        catch (LDAPException le)
1505        {
1506          if (le.getResultCode().equals(ResultCode.NO_SUCH_OBJECT))
1507          {
1508            return null;
1509          }
1510          else
1511          {
1512            throw le;
1513          }
1514        }
1515    
1516        if (! result.getResultCode().equals(ResultCode.SUCCESS))
1517        {
1518          throw new LDAPException(result);
1519        }
1520    
1521        final List<SearchResultEntry> entryList = result.getSearchEntries();
1522        if (entryList.isEmpty())
1523        {
1524          return null;
1525        }
1526        else
1527        {
1528          return entryList.get(0);
1529        }
1530      }
1531    
1532    
1533    
1534      /**
1535       * Processes an abandon request with the provided information.
1536       *
1537       * @param  requestID  The async request ID for the request to abandon.
1538       *
1539       * @throws  LDAPException  If a problem occurs while sending the request to
1540       *                         the server.
1541       */
1542      public void abandon(final AsyncRequestID requestID)
1543             throws LDAPException
1544      {
1545        abandon(requestID, null);
1546      }
1547    
1548    
1549    
1550      /**
1551       * Processes an abandon request with the provided information.
1552       *
1553       * @param  requestID  The async request ID for the request to abandon.
1554       * @param  controls   The set of controls to include in the abandon request.
1555       *                    It may be {@code null} or empty if there are no
1556       *                    controls.
1557       *
1558       * @throws  LDAPException  If a problem occurs while sending the request to
1559       *                         the server.
1560       */
1561      public void abandon(final AsyncRequestID requestID, final Control[] controls)
1562             throws LDAPException
1563      {
1564        if (debugEnabled(DebugType.LDAP))
1565        {
1566          debug(Level.INFO, DebugType.LDAP,
1567                "Sending LDAP abandon request for message ID " + requestID);
1568        }
1569    
1570        if (synchronousMode())
1571        {
1572          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1573               ERR_ABANDON_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1574        }
1575    
1576        connectionStatistics.incrementNumAbandonRequests();
1577        sendMessage(new LDAPMessage(nextMessageID(),
1578             new AbandonRequestProtocolOp(requestID.getMessageID()), controls));
1579      }
1580    
1581    
1582    
1583      /**
1584       * Sends an abandon request with the provided information.
1585       *
1586       * @param  messageID  The message ID for the request to abandon.
1587       * @param  controls   The set of controls to include in the abandon request.
1588       *                    It may be {@code null} or empty if there are no
1589       *                    controls.
1590       *
1591       * @throws  LDAPException  If a problem occurs while sending the request to
1592       *                         the server.
1593       */
1594      void abandon(final int messageID, final Control... controls)
1595           throws LDAPException
1596      {
1597        if (debugEnabled(DebugType.LDAP))
1598        {
1599          debug(Level.INFO, DebugType.LDAP,
1600                "Sending LDAP abandon request for message ID " + messageID);
1601        }
1602    
1603        connectionStatistics.incrementNumAbandonRequests();
1604        sendMessage(new LDAPMessage(nextMessageID(),
1605             new AbandonRequestProtocolOp(messageID), controls));
1606      }
1607    
1608    
1609    
1610      /**
1611       * Processes an add operation with the provided information.
1612       *
1613       * @param  dn          The DN of the entry to add.  It must not be
1614       *                     {@code null}.
1615       * @param  attributes  The set of attributes to include in the entry to add.
1616       *                     It must not be {@code null}.
1617       *
1618       * @return  The result of processing the add operation.
1619       *
1620       * @throws  LDAPException  If the server rejects the add request, or if a
1621       *                         problem is encountered while sending the request or
1622       *                         reading the response.
1623       */
1624      public LDAPResult add(final String dn, final Attribute... attributes)
1625             throws LDAPException
1626      {
1627        ensureNotNull(dn, attributes);
1628    
1629        return add(new AddRequest(dn, attributes));
1630      }
1631    
1632    
1633    
1634      /**
1635       * Processes an add operation with the provided information.
1636       *
1637       * @param  dn          The DN of the entry to add.  It must not be
1638       *                     {@code null}.
1639       * @param  attributes  The set of attributes to include in the entry to add.
1640       *                     It must not be {@code null}.
1641       *
1642       * @return  The result of processing the add operation.
1643       *
1644       * @throws  LDAPException  If the server rejects the add request, or if a
1645       *                         problem is encountered while sending the request or
1646       *                         reading the response.
1647       */
1648      public LDAPResult add(final String dn, final Collection<Attribute> attributes)
1649             throws LDAPException
1650      {
1651        ensureNotNull(dn, attributes);
1652    
1653        return add(new AddRequest(dn, attributes));
1654      }
1655    
1656    
1657    
1658      /**
1659       * Processes an add operation with the provided information.
1660       *
1661       * @param  entry  The entry to add.  It must not be {@code null}.
1662       *
1663       * @return  The result of processing the add operation.
1664       *
1665       * @throws  LDAPException  If the server rejects the add request, or if a
1666       *                         problem is encountered while sending the request or
1667       *                         reading the response.
1668       */
1669      public LDAPResult add(final Entry entry)
1670             throws LDAPException
1671      {
1672        ensureNotNull(entry);
1673    
1674        return add(new AddRequest(entry));
1675      }
1676    
1677    
1678    
1679      /**
1680       * Processes an add operation with the provided information.
1681       *
1682       * @param  ldifLines  The lines that comprise an LDIF representation of the
1683       *                    entry to add.  It must not be empty or {@code null}.
1684       *
1685       * @return  The result of processing the add operation.
1686       *
1687       * @throws  LDIFException  If the provided entry lines cannot be decoded as an
1688       *                         entry in LDIF form.
1689       *
1690       * @throws  LDAPException  If the server rejects the add request, or if a
1691       *                         problem is encountered while sending the request or
1692       *                         reading the response.
1693       */
1694      public LDAPResult add(final String... ldifLines)
1695             throws LDIFException, LDAPException
1696      {
1697        return add(new AddRequest(ldifLines));
1698      }
1699    
1700    
1701    
1702      /**
1703       * Processes the provided add request.
1704       *
1705       * @param  addRequest  The add request to be processed.  It must not be
1706       *                     {@code null}.
1707       *
1708       * @return  The result of processing the add operation.
1709       *
1710       * @throws  LDAPException  If the server rejects the add request, or if a
1711       *                         problem is encountered while sending the request or
1712       *                         reading the response.
1713       */
1714      public LDAPResult add(final AddRequest addRequest)
1715             throws LDAPException
1716      {
1717        ensureNotNull(addRequest);
1718    
1719        final LDAPResult ldapResult = addRequest.process(this, 1);
1720    
1721        switch (ldapResult.getResultCode().intValue())
1722        {
1723          case ResultCode.SUCCESS_INT_VALUE:
1724          case ResultCode.NO_OPERATION_INT_VALUE:
1725            return ldapResult;
1726    
1727          default:
1728            throw new LDAPException(ldapResult);
1729        }
1730      }
1731    
1732    
1733    
1734      /**
1735       * Processes the provided add request.
1736       *
1737       * @param  addRequest  The add request to be processed.  It must not be
1738       *                     {@code null}.
1739       *
1740       * @return  The result of processing the add operation.
1741       *
1742       * @throws  LDAPException  If the server rejects the add request, or if a
1743       *                         problem is encountered while sending the request or
1744       *                         reading the response.
1745       */
1746      public LDAPResult add(final ReadOnlyAddRequest addRequest)
1747             throws LDAPException
1748      {
1749        return add((AddRequest) addRequest);
1750      }
1751    
1752    
1753    
1754      /**
1755       * Processes the provided add request as an asynchronous operation.
1756       *
1757       * @param  addRequest      The add request to be processed.  It must not be
1758       *                         {@code null}.
1759       * @param  resultListener  The async result listener to use to handle the
1760       *                         response for the add operation.  It may be
1761       *                         {@code null} if the result is going to be obtained
1762       *                         from the returned {@code AsyncRequestID} object via
1763       *                         the {@code Future} API.
1764       *
1765       * @return  An async request ID that may be used to reference the operation.
1766       *
1767       * @throws  LDAPException  If a problem occurs while sending the request.
1768       */
1769      public AsyncRequestID asyncAdd(final AddRequest addRequest,
1770                                     final AsyncResultListener resultListener)
1771             throws LDAPException
1772      {
1773        ensureNotNull(addRequest);
1774    
1775        if (synchronousMode())
1776        {
1777          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1778               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1779        }
1780    
1781        final AsyncResultListener listener;
1782        if (resultListener == null)
1783        {
1784          listener = DiscardAsyncListener.getInstance();
1785        }
1786        else
1787        {
1788          listener = resultListener;
1789        }
1790    
1791        return addRequest.processAsync(this, listener);
1792      }
1793    
1794    
1795    
1796      /**
1797       * Processes the provided add request as an asynchronous operation.
1798       *
1799       * @param  addRequest      The add request to be processed.  It must not be
1800       *                         {@code null}.
1801       * @param  resultListener  The async result listener to use to handle the
1802       *                         response for the add operation.  It may be
1803       *                         {@code null} if the result is going to be obtained
1804       *                         from the returned {@code AsyncRequestID} object via
1805       *                         the {@code Future} API.
1806       *
1807       * @return  An async request ID that may be used to reference the operation.
1808       *
1809       * @throws  LDAPException  If a problem occurs while sending the request.
1810       */
1811      public AsyncRequestID asyncAdd(final ReadOnlyAddRequest addRequest,
1812                                     final AsyncResultListener resultListener)
1813             throws LDAPException
1814      {
1815        if (synchronousMode())
1816        {
1817          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1818               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1819        }
1820    
1821        return asyncAdd((AddRequest) addRequest, resultListener);
1822      }
1823    
1824    
1825    
1826      /**
1827       * Processes a simple bind request with the provided DN and password.
1828       * <BR><BR>
1829       * The LDAP protocol specification forbids clients from attempting to perform
1830       * a bind on a connection in which one or more other operations are already in
1831       * progress.  If a bind is attempted while any operations are in progress,
1832       * then the directory server may or may not abort processing for those
1833       * operations, depending on the type of operation and how far along the
1834       * server has already gotten while processing that operation (unless the bind
1835       * request is one that will not cause the server to attempt to change the
1836       * identity of this connection, for example by including the retain identity
1837       * request control in the bind request if using the Commercial Edition of the
1838       * LDAP SDK in conjunction with an UnboundID Directory Server).  It is
1839       * recommended that all active operations be abandoned, canceled, or allowed
1840       * to complete before attempting to perform a bind on an active connection.
1841       *
1842       * @param  bindDN    The bind DN for the bind operation.
1843       * @param  password  The password for the simple bind operation.
1844       *
1845       * @return  The result of processing the bind operation.
1846       *
1847       * @throws  LDAPException  If the server rejects the bind request, or if a
1848       *                         problem occurs while sending the request or reading
1849       *                         the response.
1850       */
1851      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1852      public BindResult bind(final String bindDN, final String password)
1853             throws LDAPException
1854      {
1855        return bind(new SimpleBindRequest(bindDN, password));
1856      }
1857    
1858    
1859    
1860      /**
1861       * Processes the provided bind request.
1862       * <BR><BR>
1863       * The LDAP protocol specification forbids clients from attempting to perform
1864       * a bind on a connection in which one or more other operations are already in
1865       * progress.  If a bind is attempted while any operations are in progress,
1866       * then the directory server may or may not abort processing for those
1867       * operations, depending on the type of operation and how far along the
1868       * server has already gotten while processing that operation (unless the bind
1869       * request is one that will not cause the server to attempt to change the
1870       * identity of this connection, for example by including the retain identity
1871       * request control in the bind request if using the Commercial Edition of the
1872       * LDAP SDK in conjunction with an UnboundID Directory Server).  It is
1873       * recommended that all active operations be abandoned, canceled, or allowed
1874       * to complete before attempting to perform a bind on an active connection.
1875       *
1876       * @param  bindRequest  The bind request to be processed.  It must not be
1877       *                      {@code null}.
1878       *
1879       * @return  The result of processing the bind operation.
1880       *
1881       * @throws  LDAPException  If the server rejects the bind request, or if a
1882       *                         problem occurs while sending the request or reading
1883       *                         the response.
1884       */
1885      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1886      public BindResult bind(final BindRequest bindRequest)
1887             throws LDAPException
1888      {
1889        ensureNotNull(bindRequest);
1890    
1891        lastBindRequest = null;
1892    
1893        final BindResult bindResult = bindRequest.process(this, 1);
1894    
1895        if (bindResult.getResultCode().equals(ResultCode.SUCCESS))
1896        {
1897          // We don't want to update the last bind request or update the cached
1898          // schema for this connection if it included the retain identity control.
1899          // However, that's only available in the Commercial Edition, so just
1900          // reference it by OID here.
1901          boolean hasRetainIdentityControl = false;
1902          for (final Control c : bindRequest.getControls())
1903          {
1904            if (c.getOID().equals("1.3.6.1.4.1.30221.2.5.3"))
1905            {
1906              hasRetainIdentityControl = true;
1907              break;
1908            }
1909          }
1910    
1911          if (! hasRetainIdentityControl)
1912          {
1913            lastBindRequest = bindRequest;
1914    
1915            if (connectionOptions.useSchema())
1916            {
1917              try
1918              {
1919                cachedSchema = getCachedSchema(this);
1920              }
1921              catch (Exception e)
1922              {
1923                debugException(e);
1924              }
1925            }
1926          }
1927    
1928          return bindResult;
1929        }
1930    
1931        if (bindResult.getResultCode().equals(ResultCode.SASL_BIND_IN_PROGRESS))
1932        {
1933          throw new SASLBindInProgressException(bindResult);
1934        }
1935        else
1936        {
1937          throw new LDAPException(bindResult);
1938        }
1939      }
1940    
1941    
1942    
1943      /**
1944       * Processes a compare operation with the provided information.
1945       *
1946       * @param  dn              The DN of the entry in which to make the
1947       *                         comparison.  It must not be {@code null}.
1948       * @param  attributeName   The attribute name for which to make the
1949       *                         comparison.  It must not be {@code null}.
1950       * @param  assertionValue  The assertion value to verify in the target entry.
1951       *                         It must not be {@code null}.
1952       *
1953       * @return  The result of processing the compare operation.
1954       *
1955       * @throws  LDAPException  If the server rejects the compare request, or if a
1956       *                         problem is encountered while sending the request or
1957       *                         reading the response.
1958       */
1959      public CompareResult compare(final String dn, final String attributeName,
1960                                   final String assertionValue)
1961             throws LDAPException
1962      {
1963        ensureNotNull(dn, attributeName, assertionValue);
1964    
1965        return compare(new CompareRequest(dn, attributeName, assertionValue));
1966      }
1967    
1968    
1969    
1970      /**
1971       * Processes the provided compare request.
1972       *
1973       * @param  compareRequest  The compare request to be processed.  It must not
1974       *                         be {@code null}.
1975       *
1976       * @return  The result of processing the compare operation.
1977       *
1978       * @throws  LDAPException  If the server rejects the compare request, or if a
1979       *                         problem is encountered while sending the request or
1980       *                         reading the response.
1981       */
1982      public CompareResult compare(final CompareRequest compareRequest)
1983             throws LDAPException
1984      {
1985        ensureNotNull(compareRequest);
1986    
1987        final LDAPResult result = compareRequest.process(this, 1);
1988        switch (result.getResultCode().intValue())
1989        {
1990          case ResultCode.COMPARE_FALSE_INT_VALUE:
1991          case ResultCode.COMPARE_TRUE_INT_VALUE:
1992            return new CompareResult(result);
1993    
1994          default:
1995            throw new LDAPException(result);
1996        }
1997      }
1998    
1999    
2000    
2001      /**
2002       * Processes the provided compare request.
2003       *
2004       * @param  compareRequest  The compare request to be processed.  It must not
2005       *                         be {@code null}.
2006       *
2007       * @return  The result of processing the compare operation.
2008       *
2009       * @throws  LDAPException  If the server rejects the compare request, or if a
2010       *                         problem is encountered while sending the request or
2011       *                         reading the response.
2012       */
2013      public CompareResult compare(final ReadOnlyCompareRequest compareRequest)
2014             throws LDAPException
2015      {
2016        return compare((CompareRequest) compareRequest);
2017      }
2018    
2019    
2020    
2021      /**
2022       * Processes the provided compare request as an asynchronous operation.
2023       *
2024       * @param  compareRequest  The compare request to be processed.  It must not
2025       *                         be {@code null}.
2026       * @param  resultListener  The async result listener to use to handle the
2027       *                         response for the compare operation.  It may be
2028       *                         {@code null} if the result is going to be obtained
2029       *                         from the returned {@code AsyncRequestID} object via
2030       *                         the {@code Future} API.
2031       *
2032       * @return  An async request ID that may be used to reference the operation.
2033       *
2034       * @throws  LDAPException  If a problem occurs while sending the request.
2035       */
2036      public AsyncRequestID asyncCompare(final CompareRequest compareRequest,
2037                                 final AsyncCompareResultListener resultListener)
2038             throws LDAPException
2039      {
2040        ensureNotNull(compareRequest);
2041    
2042        if (synchronousMode())
2043        {
2044          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2045               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2046        }
2047    
2048        final AsyncCompareResultListener listener;
2049        if (resultListener == null)
2050        {
2051          listener = DiscardAsyncListener.getInstance();
2052        }
2053        else
2054        {
2055          listener = resultListener;
2056        }
2057    
2058        return compareRequest.processAsync(this, listener);
2059      }
2060    
2061    
2062    
2063      /**
2064       * Processes the provided compare request as an asynchronous operation.
2065       *
2066       * @param  compareRequest  The compare request to be processed.  It must not
2067       *                         be {@code null}.
2068       * @param  resultListener  The async result listener to use to handle the
2069       *                         response for the compare operation.  It may be
2070       *                         {@code null} if the result is going to be obtained
2071       *                         from the returned {@code AsyncRequestID} object via
2072       *                         the {@code Future} API.
2073       *
2074       * @return  An async request ID that may be used to reference the operation.
2075       *
2076       * @throws  LDAPException  If a problem occurs while sending the request.
2077       */
2078      public AsyncRequestID asyncCompare(
2079                                 final ReadOnlyCompareRequest compareRequest,
2080                                 final AsyncCompareResultListener resultListener)
2081             throws LDAPException
2082      {
2083        if (synchronousMode())
2084        {
2085          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2086               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2087        }
2088    
2089        return asyncCompare((CompareRequest) compareRequest, resultListener);
2090      }
2091    
2092    
2093    
2094      /**
2095       * Deletes the entry with the specified DN.
2096       *
2097       * @param  dn  The DN of the entry to delete.  It must not be {@code null}.
2098       *
2099       * @return  The result of processing the delete operation.
2100       *
2101       * @throws  LDAPException  If the server rejects the delete request, or if a
2102       *                         problem is encountered while sending the request or
2103       *                         reading the response.
2104       */
2105      public LDAPResult delete(final String dn)
2106             throws LDAPException
2107      {
2108        return delete(new DeleteRequest(dn));
2109      }
2110    
2111    
2112    
2113      /**
2114       * Processes the provided delete request.
2115       *
2116       * @param  deleteRequest  The delete request to be processed.  It must not be
2117       *                        {@code null}.
2118       *
2119       * @return  The result of processing the delete operation.
2120       *
2121       * @throws  LDAPException  If the server rejects the delete request, or if a
2122       *                         problem is encountered while sending the request or
2123       *                         reading the response.
2124       */
2125      public LDAPResult delete(final DeleteRequest deleteRequest)
2126             throws LDAPException
2127      {
2128        ensureNotNull(deleteRequest);
2129    
2130        final LDAPResult ldapResult = deleteRequest.process(this, 1);
2131    
2132        switch (ldapResult.getResultCode().intValue())
2133        {
2134          case ResultCode.SUCCESS_INT_VALUE:
2135          case ResultCode.NO_OPERATION_INT_VALUE:
2136            return ldapResult;
2137    
2138          default:
2139            throw new LDAPException(ldapResult);
2140        }
2141      }
2142    
2143    
2144    
2145      /**
2146       * Processes the provided delete request.
2147       *
2148       * @param  deleteRequest  The delete request to be processed.  It must not be
2149       *                        {@code null}.
2150       *
2151       * @return  The result of processing the delete operation.
2152       *
2153       * @throws  LDAPException  If the server rejects the delete request, or if a
2154       *                         problem is encountered while sending the request or
2155       *                         reading the response.
2156       */
2157      public LDAPResult delete(final ReadOnlyDeleteRequest deleteRequest)
2158             throws LDAPException
2159      {
2160        return delete((DeleteRequest) deleteRequest);
2161      }
2162    
2163    
2164    
2165      /**
2166       * Processes the provided delete request as an asynchronous operation.
2167       *
2168       * @param  deleteRequest   The delete request to be processed.  It must not be
2169       *                         {@code null}.
2170       * @param  resultListener  The async result listener to use to handle the
2171       *                         response for the delete operation.  It may be
2172       *                         {@code null} if the result is going to be obtained
2173       *                         from the returned {@code AsyncRequestID} object via
2174       *                         the {@code Future} API.
2175       *
2176       * @return  An async request ID that may be used to reference the operation.
2177       *
2178       * @throws  LDAPException  If a problem occurs while sending the request.
2179       */
2180      public AsyncRequestID asyncDelete(final DeleteRequest deleteRequest,
2181                                 final AsyncResultListener resultListener)
2182             throws LDAPException
2183      {
2184        ensureNotNull(deleteRequest);
2185    
2186        if (synchronousMode())
2187        {
2188          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2189               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2190        }
2191    
2192        final AsyncResultListener listener;
2193        if (resultListener == null)
2194        {
2195          listener = DiscardAsyncListener.getInstance();
2196        }
2197        else
2198        {
2199          listener = resultListener;
2200        }
2201    
2202        return deleteRequest.processAsync(this, listener);
2203      }
2204    
2205    
2206    
2207      /**
2208       * Processes the provided delete request as an asynchronous operation.
2209       *
2210       * @param  deleteRequest   The delete request to be processed.  It must not be
2211       *                         {@code null}.
2212       * @param  resultListener  The async result listener to use to handle the
2213       *                         response for the delete operation.  It may be
2214       *                         {@code null} if the result is going to be obtained
2215       *                         from the returned {@code AsyncRequestID} object via
2216       *                         the {@code Future} API.
2217       *
2218       * @return  An async request ID that may be used to reference the operation.
2219       *
2220       * @throws  LDAPException  If a problem occurs while sending the request.
2221       */
2222      public AsyncRequestID asyncDelete(final ReadOnlyDeleteRequest deleteRequest,
2223                                 final AsyncResultListener resultListener)
2224             throws LDAPException
2225      {
2226        if (synchronousMode())
2227        {
2228          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2229               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2230        }
2231    
2232        return asyncDelete((DeleteRequest) deleteRequest, resultListener);
2233      }
2234    
2235    
2236    
2237      /**
2238       * Processes an extended request with the provided request OID.  Note that
2239       * because some types of extended operations return unusual result codes under
2240       * "normal" conditions, the server may not always throw an exception for a
2241       * failed extended operation like it does for other types of operations.  It
2242       * will throw an exception under conditions where there appears to be a
2243       * problem with the connection or the server to which the connection is
2244       * established, but there may be many circumstances in which an extended
2245       * operation is not processed correctly but this method does not throw an
2246       * exception.  In the event that no exception is thrown, it is the
2247       * responsibility of the caller to interpret the result to determine whether
2248       * the operation was processed as expected.
2249       * <BR><BR>
2250       * Note that extended operations which may change the state of this connection
2251       * (e.g., the StartTLS extended operation, which will add encryption to a
2252       * previously-unencrypted connection) should not be invoked while any other
2253       * operations are active on the connection.  It is recommended that all active
2254       * operations be abandoned, canceled, or allowed to complete before attempting
2255       * to process an extended operation that may change the state of this
2256       * connection.
2257       *
2258       * @param  requestOID  The OID for the extended request to process.  It must
2259       *                     not be {@code null}.
2260       *
2261       * @return  The extended result object that provides information about the
2262       *          result of the request processing.  It may or may not indicate that
2263       *          the operation was successful.
2264       *
2265       * @throws  LDAPException  If a problem occurs while sending the request or
2266       *                         reading the response.
2267       */
2268      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2269      public ExtendedResult processExtendedOperation(final String requestOID)
2270             throws LDAPException
2271      {
2272        ensureNotNull(requestOID);
2273    
2274        return processExtendedOperation(new ExtendedRequest(requestOID));
2275      }
2276    
2277    
2278    
2279      /**
2280       * Processes an extended request with the provided request OID and value.
2281       * Note that because some types of extended operations return unusual result
2282       * codes under "normal" conditions, the server may not always throw an
2283       * exception for a failed extended operation like it does for other types of
2284       * operations.  It will throw an exception under conditions where there
2285       * appears to be a problem with the connection or the server to which the
2286       * connection is established, but there may be many circumstances in which an
2287       * extended operation is not processed correctly but this method does not
2288       * throw an exception.  In the event that no exception is thrown, it is the
2289       * responsibility of the caller to interpret the result to determine whether
2290       * the operation was processed as expected.
2291       * <BR><BR>
2292       * Note that extended operations which may change the state of this connection
2293       * (e.g., the StartTLS extended operation, which will add encryption to a
2294       * previously-unencrypted connection) should not be invoked while any other
2295       * operations are active on the connection.  It is recommended that all active
2296       * operations be abandoned, canceled, or allowed to complete before attempting
2297       * to process an extended operation that may change the state of this
2298       * connection.
2299       *
2300       * @param  requestOID    The OID for the extended request to process.  It must
2301       *                       not be {@code null}.
2302       * @param  requestValue  The encoded value for the extended request to
2303       *                       process.  It may be {@code null} if there does not
2304       *                       need to be a value for the requested operation.
2305       *
2306       * @return  The extended result object that provides information about the
2307       *          result of the request processing.  It may or may not indicate that
2308       *          the operation was successful.
2309       *
2310       * @throws  LDAPException  If a problem occurs while sending the request or
2311       *                         reading the response.
2312       */
2313      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2314      public ExtendedResult processExtendedOperation(final String requestOID,
2315                                 final ASN1OctetString requestValue)
2316             throws LDAPException
2317      {
2318        ensureNotNull(requestOID);
2319    
2320        return processExtendedOperation(new ExtendedRequest(requestOID,
2321                                                            requestValue));
2322      }
2323    
2324    
2325    
2326      /**
2327       * Processes the provided extended request.  Note that because some types of
2328       * extended operations return unusual result codes under "normal" conditions,
2329       * the server may not always throw an exception for a failed extended
2330       * operation like it does for other types of operations.  It will throw an
2331       * exception under conditions where there appears to be a problem with the
2332       * connection or the server to which the connection is established, but there
2333       * may be many circumstances in which an extended operation is not processed
2334       * correctly but this method does not throw an exception.  In the event that
2335       * no exception is thrown, it is the responsibility of the caller to interpret
2336       * the result to determine whether the operation was processed as expected.
2337       * <BR><BR>
2338       * Note that extended operations which may change the state of this connection
2339       * (e.g., the StartTLS extended operation, which will add encryption to a
2340       * previously-unencrypted connection) should not be invoked while any other
2341       * operations are active on the connection.  It is recommended that all active
2342       * operations be abandoned, canceled, or allowed to complete before attempting
2343       * to process an extended operation that may change the state of this
2344       * connection.
2345       *
2346       * @param  extendedRequest  The extended request to be processed.  It must not
2347       *                          be {@code null}.
2348       *
2349       * @return  The extended result object that provides information about the
2350       *          result of the request processing.  It may or may not indicate that
2351       *          the operation was successful.
2352       *
2353       * @throws  LDAPException  If a problem occurs while sending the request or
2354       *                         reading the response.
2355       */
2356      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2357      public ExtendedResult processExtendedOperation(
2358                                   final ExtendedRequest extendedRequest)
2359             throws LDAPException
2360      {
2361        ensureNotNull(extendedRequest);
2362    
2363        final ExtendedResult extendedResult = extendedRequest.process(this, 1);
2364    
2365        if ((extendedResult.getOID() == null) &&
2366            (extendedResult.getValue() == null))
2367        {
2368          switch (extendedResult.getResultCode().intValue())
2369          {
2370            case ResultCode.OPERATIONS_ERROR_INT_VALUE:
2371            case ResultCode.PROTOCOL_ERROR_INT_VALUE:
2372            case ResultCode.BUSY_INT_VALUE:
2373            case ResultCode.UNAVAILABLE_INT_VALUE:
2374            case ResultCode.OTHER_INT_VALUE:
2375            case ResultCode.SERVER_DOWN_INT_VALUE:
2376            case ResultCode.LOCAL_ERROR_INT_VALUE:
2377            case ResultCode.ENCODING_ERROR_INT_VALUE:
2378            case ResultCode.DECODING_ERROR_INT_VALUE:
2379            case ResultCode.TIMEOUT_INT_VALUE:
2380            case ResultCode.NO_MEMORY_INT_VALUE:
2381            case ResultCode.CONNECT_ERROR_INT_VALUE:
2382              throw new LDAPException(extendedResult);
2383          }
2384        }
2385    
2386        return extendedResult;
2387      }
2388    
2389    
2390    
2391      /**
2392       * Applies the provided modification to the specified entry.
2393       *
2394       * @param  dn   The DN of the entry to modify.  It must not be {@code null}.
2395       * @param  mod  The modification to apply to the target entry.  It must not
2396       *              be {@code null}.
2397       *
2398       * @return  The result of processing the modify operation.
2399       *
2400       * @throws  LDAPException  If the server rejects the modify request, or if a
2401       *                         problem is encountered while sending the request or
2402       *                         reading the response.
2403       */
2404      public LDAPResult modify(final String dn, final Modification mod)
2405             throws LDAPException
2406      {
2407        ensureNotNull(dn, mod);
2408    
2409        return modify(new ModifyRequest(dn, mod));
2410      }
2411    
2412    
2413    
2414      /**
2415       * Applies the provided set of modifications to the specified entry.
2416       *
2417       * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
2418       * @param  mods  The set of modifications to apply to the target entry.  It
2419       *               must not be {@code null} or empty.  *
2420       * @return  The result of processing the modify operation.
2421       *
2422       * @throws  LDAPException  If the server rejects the modify request, or if a
2423       *                         problem is encountered while sending the request or
2424       *                         reading the response.
2425       */
2426      public LDAPResult modify(final String dn, final Modification... mods)
2427             throws LDAPException
2428      {
2429        ensureNotNull(dn, mods);
2430    
2431        return modify(new ModifyRequest(dn, mods));
2432      }
2433    
2434    
2435    
2436      /**
2437       * Applies the provided set of modifications to the specified entry.
2438       *
2439       * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
2440       * @param  mods  The set of modifications to apply to the target entry.  It
2441       *               must not be {@code null} or empty.
2442       *
2443       * @return  The result of processing the modify operation.
2444       *
2445       * @throws  LDAPException  If the server rejects the modify request, or if a
2446       *                         problem is encountered while sending the request or
2447       *                         reading the response.
2448       */
2449      public LDAPResult modify(final String dn, final List<Modification> mods)
2450             throws LDAPException
2451      {
2452        ensureNotNull(dn, mods);
2453    
2454        return modify(new ModifyRequest(dn, mods));
2455      }
2456    
2457    
2458    
2459      /**
2460       * Processes a modify request from the provided LDIF representation of the
2461       * changes.
2462       *
2463       * @param  ldifModificationLines  The lines that comprise an LDIF
2464       *                                representation of a modify change record.
2465       *                                It must not be {@code null} or empty.
2466       *
2467       * @return  The result of processing the modify operation.
2468       *
2469       * @throws  LDIFException  If the provided set of lines cannot be parsed as an
2470       *                         LDIF modify change record.
2471       *
2472       * @throws  LDAPException  If the server rejects the modify request, or if a
2473       *                         problem is encountered while sending the request or
2474       *                         reading the response.
2475       *
2476       */
2477      public LDAPResult modify(final String... ldifModificationLines)
2478             throws LDIFException, LDAPException
2479      {
2480        ensureNotNull(ldifModificationLines);
2481    
2482        return modify(new ModifyRequest(ldifModificationLines));
2483      }
2484    
2485    
2486    
2487      /**
2488       * Processes the provided modify request.
2489       *
2490       * @param  modifyRequest  The modify request to be processed.  It must not be
2491       *                        {@code null}.
2492       *
2493       * @return  The result of processing the modify operation.
2494       *
2495       * @throws  LDAPException  If the server rejects the modify request, or if a
2496       *                         problem is encountered while sending the request or
2497       *                         reading the response.
2498       */
2499      public LDAPResult modify(final ModifyRequest modifyRequest)
2500             throws LDAPException
2501      {
2502        ensureNotNull(modifyRequest);
2503    
2504        final LDAPResult ldapResult = modifyRequest.process(this, 1);
2505    
2506        switch (ldapResult.getResultCode().intValue())
2507        {
2508          case ResultCode.SUCCESS_INT_VALUE:
2509          case ResultCode.NO_OPERATION_INT_VALUE:
2510            return ldapResult;
2511    
2512          default:
2513            throw new LDAPException(ldapResult);
2514        }
2515      }
2516    
2517    
2518    
2519      /**
2520       * Processes the provided modify request.
2521       *
2522       * @param  modifyRequest  The modify request to be processed.  It must not be
2523       *                        {@code null}.
2524       *
2525       * @return  The result of processing the modify operation.
2526       *
2527       * @throws  LDAPException  If the server rejects the modify request, or if a
2528       *                         problem is encountered while sending the request or
2529       *                         reading the response.
2530       */
2531      public LDAPResult modify(final ReadOnlyModifyRequest modifyRequest)
2532             throws LDAPException
2533      {
2534        return modify((ModifyRequest) modifyRequest);
2535      }
2536    
2537    
2538    
2539      /**
2540       * Processes the provided modify request as an asynchronous operation.
2541       *
2542       * @param  modifyRequest   The modify request to be processed.  It must not be
2543       *                         {@code null}.
2544       * @param  resultListener  The async result listener to use to handle the
2545       *                         response for the modify operation.  It may be
2546       *                         {@code null} if the result is going to be obtained
2547       *                         from the returned {@code AsyncRequestID} object via
2548       *                         the {@code Future} API.
2549       *
2550       * @return  An async request ID that may be used to reference the operation.
2551       *
2552       * @throws  LDAPException  If a problem occurs while sending the request.
2553       */
2554      public AsyncRequestID asyncModify(final ModifyRequest modifyRequest,
2555                                 final AsyncResultListener resultListener)
2556             throws LDAPException
2557      {
2558        ensureNotNull(modifyRequest);
2559    
2560        if (synchronousMode())
2561        {
2562          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2563               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2564        }
2565    
2566        final AsyncResultListener listener;
2567        if (resultListener == null)
2568        {
2569          listener = DiscardAsyncListener.getInstance();
2570        }
2571        else
2572        {
2573          listener = resultListener;
2574        }
2575    
2576        return modifyRequest.processAsync(this, listener);
2577      }
2578    
2579    
2580    
2581      /**
2582       * Processes the provided modify request as an asynchronous operation.
2583       *
2584       * @param  modifyRequest   The modify request to be processed.  It must not be
2585       *                         {@code null}.
2586       * @param  resultListener  The async result listener to use to handle the
2587       *                         response for the modify operation.  It may be
2588       *                         {@code null} if the result is going to be obtained
2589       *                         from the returned {@code AsyncRequestID} object via
2590       *                         the {@code Future} API.
2591       *
2592       * @return  An async request ID that may be used to reference the operation.
2593       *
2594       * @throws  LDAPException  If a problem occurs while sending the request.
2595       */
2596      public AsyncRequestID asyncModify(final ReadOnlyModifyRequest modifyRequest,
2597                                 final AsyncResultListener resultListener)
2598             throws LDAPException
2599      {
2600        if (synchronousMode())
2601        {
2602          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2603               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2604        }
2605    
2606        return asyncModify((ModifyRequest) modifyRequest, resultListener);
2607      }
2608    
2609    
2610    
2611      /**
2612       * Performs a modify DN operation with the provided information.
2613       *
2614       * @param  dn            The current DN for the entry to rename.  It must not
2615       *                       be {@code null}.
2616       * @param  newRDN        The new RDN to use for the entry.  It must not be
2617       *                       {@code null}.
2618       * @param  deleteOldRDN  Indicates whether to delete the current RDN value
2619       *                       from the entry.
2620       *
2621       * @return  The result of processing the modify DN operation.
2622       *
2623       * @throws  LDAPException  If the server rejects the modify DN request, or if
2624       *                         a problem is encountered while sending the request
2625       *                         or reading the response.
2626       */
2627      public LDAPResult modifyDN(final String dn, final String newRDN,
2628                                 final boolean deleteOldRDN)
2629             throws LDAPException
2630      {
2631        ensureNotNull(dn, newRDN);
2632    
2633        return modifyDN(new ModifyDNRequest(dn, newRDN, deleteOldRDN));
2634      }
2635    
2636    
2637    
2638      /**
2639       * Performs a modify DN operation with the provided information.
2640       *
2641       * @param  dn             The current DN for the entry to rename.  It must not
2642       *                        be {@code null}.
2643       * @param  newRDN         The new RDN to use for the entry.  It must not be
2644       *                        {@code null}.
2645       * @param  deleteOldRDN   Indicates whether to delete the current RDN value
2646       *                        from the entry.
2647       * @param  newSuperiorDN  The new superior DN for the entry.  It may be
2648       *                        {@code null} if the entry is not to be moved below a
2649       *                        new parent.
2650       *
2651       * @return  The result of processing the modify DN operation.
2652       *
2653       * @throws  LDAPException  If the server rejects the modify DN request, or if
2654       *                         a problem is encountered while sending the request
2655       *                         or reading the response.
2656       */
2657      public LDAPResult modifyDN(final String dn, final String newRDN,
2658                                 final boolean deleteOldRDN,
2659                                 final String newSuperiorDN)
2660             throws LDAPException
2661      {
2662        ensureNotNull(dn, newRDN);
2663    
2664        return modifyDN(new ModifyDNRequest(dn, newRDN, deleteOldRDN,
2665                                            newSuperiorDN));
2666      }
2667    
2668    
2669    
2670      /**
2671       * Processes the provided modify DN request.
2672       *
2673       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2674       *                          not be {@code null}.
2675       *
2676       * @return  The result of processing the modify DN operation.
2677       *
2678       * @throws  LDAPException  If the server rejects the modify DN request, or if
2679       *                         a problem is encountered while sending the request
2680       *                         or reading the response.
2681       */
2682      public LDAPResult modifyDN(final ModifyDNRequest modifyDNRequest)
2683             throws LDAPException
2684      {
2685        ensureNotNull(modifyDNRequest);
2686    
2687        final LDAPResult ldapResult = modifyDNRequest.process(this, 1);
2688    
2689        switch (ldapResult.getResultCode().intValue())
2690        {
2691          case ResultCode.SUCCESS_INT_VALUE:
2692          case ResultCode.NO_OPERATION_INT_VALUE:
2693            return ldapResult;
2694    
2695          default:
2696            throw new LDAPException(ldapResult);
2697        }
2698      }
2699    
2700    
2701    
2702      /**
2703       * Processes the provided modify DN request.
2704       *
2705       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2706       *                          not be {@code null}.
2707       *
2708       * @return  The result of processing the modify DN operation.
2709       *
2710       * @throws  LDAPException  If the server rejects the modify DN request, or if
2711       *                         a problem is encountered while sending the request
2712       *                         or reading the response.
2713       */
2714      public LDAPResult modifyDN(final ReadOnlyModifyDNRequest modifyDNRequest)
2715             throws LDAPException
2716      {
2717        return modifyDN((ModifyDNRequest) modifyDNRequest);
2718      }
2719    
2720    
2721    
2722      /**
2723       * Processes the provided modify DN request as an asynchronous operation.
2724       *
2725       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2726       *                          not be {@code null}.
2727       * @param  resultListener  The async result listener to use to handle the
2728       *                         response for the modify DN operation.  It may be
2729       *                         {@code null} if the result is going to be obtained
2730       *                         from the returned {@code AsyncRequestID} object via
2731       *                         the {@code Future} API.
2732       *
2733       * @return  An async request ID that may be used to reference the operation.
2734       *
2735       * @throws  LDAPException  If a problem occurs while sending the request.
2736       */
2737      public AsyncRequestID asyncModifyDN(final ModifyDNRequest modifyDNRequest,
2738                                 final AsyncResultListener resultListener)
2739             throws LDAPException
2740      {
2741        ensureNotNull(modifyDNRequest);
2742    
2743        if (synchronousMode())
2744        {
2745          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2746               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2747        }
2748    
2749        final AsyncResultListener listener;
2750        if (resultListener == null)
2751        {
2752          listener = DiscardAsyncListener.getInstance();
2753        }
2754        else
2755        {
2756          listener = resultListener;
2757        }
2758    
2759        return modifyDNRequest.processAsync(this, listener);
2760      }
2761    
2762    
2763    
2764      /**
2765       * Processes the provided modify DN request as an asynchronous operation.
2766       *
2767       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2768       *                          not be {@code null}.
2769       * @param  resultListener  The async result listener to use to handle the
2770       *                         response for the modify DN operation.  It may be
2771       *                         {@code null} if the result is going to be obtained
2772       *                         from the returned {@code AsyncRequestID} object via
2773       *                         the {@code Future} API.
2774       *
2775       * @return  An async request ID that may be used to reference the operation.
2776       *
2777       * @throws  LDAPException  If a problem occurs while sending the request.
2778       */
2779      public AsyncRequestID asyncModifyDN(
2780                                 final ReadOnlyModifyDNRequest modifyDNRequest,
2781                                 final AsyncResultListener resultListener)
2782             throws LDAPException
2783      {
2784        if (synchronousMode())
2785        {
2786          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2787               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2788        }
2789    
2790        return asyncModifyDN((ModifyDNRequest) modifyDNRequest, resultListener);
2791      }
2792    
2793    
2794    
2795      /**
2796       * Processes a search operation with the provided information.  The search
2797       * result entries and references will be collected internally and included in
2798       * the {@code SearchResult} object that is returned.
2799       * <BR><BR>
2800       * Note that if the search does not complete successfully, an
2801       * {@code LDAPSearchException} will be thrown  In some cases, one or more
2802       * search result entries or references may have been returned before the
2803       * failure response is received.  In this case, the
2804       * {@code LDAPSearchException} methods like {@code getEntryCount},
2805       * {@code getSearchEntries}, {@code getReferenceCount}, and
2806       * {@code getSearchReferences} may be used to obtain information about those
2807       * entries and references.
2808       *
2809       * @param  baseDN      The base DN for the search request.  It must not be
2810       *                     {@code null}.
2811       * @param  scope       The scope that specifies the range of entries that
2812       *                     should be examined for the search.
2813       * @param  filter      The string representation of the filter to use to
2814       *                     identify matching entries.  It must not be
2815       *                     {@code null}.
2816       * @param  attributes  The set of attributes that should be returned in
2817       *                     matching entries.  It may be {@code null} or empty if
2818       *                     the default attribute set (all user attributes) is to
2819       *                     be requested.
2820       *
2821       * @return  A search result object that provides information about the
2822       *          processing of the search, including the set of matching entries
2823       *          and search references returned by the server.
2824       *
2825       * @throws  LDAPSearchException  If the search does not complete successfully,
2826       *                               or if a problem is encountered while parsing
2827       *                               the provided filter string, sending the
2828       *                               request, or reading the response.  If one
2829       *                               or more entries or references were returned
2830       *                               before the failure was encountered, then the
2831       *                               {@code LDAPSearchException} object may be
2832       *                               examined to obtain information about those
2833       *                               entries and/or references.
2834       */
2835      public SearchResult search(final String baseDN, final SearchScope scope,
2836                                 final String filter, final String... attributes)
2837             throws LDAPSearchException
2838      {
2839        ensureNotNull(baseDN, filter);
2840    
2841        try
2842        {
2843          return search(new SearchRequest(baseDN, scope, filter, attributes));
2844        }
2845        catch (LDAPSearchException lse)
2846        {
2847          debugException(lse);
2848          throw lse;
2849        }
2850        catch (LDAPException le)
2851        {
2852          debugException(le);
2853          throw new LDAPSearchException(le);
2854        }
2855      }
2856    
2857    
2858    
2859      /**
2860       * Processes a search operation with the provided information.  The search
2861       * result entries and references will be collected internally and included in
2862       * the {@code SearchResult} object that is returned.
2863       * <BR><BR>
2864       * Note that if the search does not complete successfully, an
2865       * {@code LDAPSearchException} will be thrown  In some cases, one or more
2866       * search result entries or references may have been returned before the
2867       * failure response is received.  In this case, the
2868       * {@code LDAPSearchException} methods like {@code getEntryCount},
2869       * {@code getSearchEntries}, {@code getReferenceCount}, and
2870       * {@code getSearchReferences} may be used to obtain information about those
2871       * entries and references.
2872       *
2873       * @param  baseDN      The base DN for the search request.  It must not be
2874       *                     {@code null}.
2875       * @param  scope       The scope that specifies the range of entries that
2876       *                     should be examined for the search.
2877       * @param  filter      The filter to use to identify matching entries.  It
2878       *                     must not be {@code null}.
2879       * @param  attributes  The set of attributes that should be returned in
2880       *                     matching entries.  It may be {@code null} or empty if
2881       *                     the default attribute set (all user attributes) is to
2882       *                     be requested.
2883       *
2884       * @return  A search result object that provides information about the
2885       *          processing of the search, including the set of matching entries
2886       *          and search references returned by the server.
2887       *
2888       * @throws  LDAPSearchException  If the search does not complete successfully,
2889       *                               or if a problem is encountered while sending
2890       *                               the request or reading the response.  If one
2891       *                               or more entries or references were returned
2892       *                               before the failure was encountered, then the
2893       *                               {@code LDAPSearchException} object may be
2894       *                               examined to obtain information about those
2895       *                               entries and/or references.
2896       */
2897      public SearchResult search(final String baseDN, final SearchScope scope,
2898                                 final Filter filter, final String... attributes)
2899             throws LDAPSearchException
2900      {
2901        ensureNotNull(baseDN, filter);
2902    
2903        return search(new SearchRequest(baseDN, scope, filter, attributes));
2904      }
2905    
2906    
2907    
2908      /**
2909       * Processes a search operation with the provided information.
2910       * <BR><BR>
2911       * Note that if the search does not complete successfully, an
2912       * {@code LDAPSearchException} will be thrown  In some cases, one or more
2913       * search result entries or references may have been returned before the
2914       * failure response is received.  In this case, the
2915       * {@code LDAPSearchException} methods like {@code getEntryCount},
2916       * {@code getSearchEntries}, {@code getReferenceCount}, and
2917       * {@code getSearchReferences} may be used to obtain information about those
2918       * entries and references (although if a search result listener was provided,
2919       * then it will have been used to make any entries and references available,
2920       * and they will not be available through the {@code getSearchEntries} and
2921       * {@code getSearchReferences} methods).
2922       *
2923       * @param  searchResultListener  The search result listener that should be
2924       *                               used to return results to the client.  It may
2925       *                               be {@code null} if the search results should
2926       *                               be collected internally and returned in the
2927       *                               {@code SearchResult} object.
2928       * @param  baseDN                The base DN for the search request.  It must
2929       *                               not be {@code null}.
2930       * @param  scope                 The scope that specifies the range of entries
2931       *                               that should be examined for the search.
2932       * @param  filter                The string representation of the filter to
2933       *                               use to identify matching entries.  It must
2934       *                               not be {@code null}.
2935       * @param  attributes            The set of attributes that should be returned
2936       *                               in matching entries.  It may be {@code null}
2937       *                               or empty if the default attribute set (all
2938       *                               user attributes) is to be requested.
2939       *
2940       * @return  A search result object that provides information about the
2941       *          processing of the search, potentially including the set of
2942       *          matching entries and search references returned by the server.
2943       *
2944       * @throws  LDAPSearchException  If the search does not complete successfully,
2945       *                               or if a problem is encountered while parsing
2946       *                               the provided filter string, sending the
2947       *                               request, or reading the response.  If one
2948       *                               or more entries or references were returned
2949       *                               before the failure was encountered, then the
2950       *                               {@code LDAPSearchException} object may be
2951       *                               examined to obtain information about those
2952       *                               entries and/or references.
2953       */
2954      public SearchResult search(final SearchResultListener searchResultListener,
2955                                 final String baseDN, final SearchScope scope,
2956                                 final String filter, final String... attributes)
2957             throws LDAPSearchException
2958      {
2959        ensureNotNull(baseDN, filter);
2960    
2961        try
2962        {
2963          return search(new SearchRequest(searchResultListener, baseDN, scope,
2964                                          filter, attributes));
2965        }
2966        catch (LDAPSearchException lse)
2967        {
2968          debugException(lse);
2969          throw lse;
2970        }
2971        catch (LDAPException le)
2972        {
2973          debugException(le);
2974          throw new LDAPSearchException(le);
2975        }
2976      }
2977    
2978    
2979    
2980      /**
2981       * Processes a search operation with the provided information.
2982       * <BR><BR>
2983       * Note that if the search does not complete successfully, an
2984       * {@code LDAPSearchException} will be thrown  In some cases, one or more
2985       * search result entries or references may have been returned before the
2986       * failure response is received.  In this case, the
2987       * {@code LDAPSearchException} methods like {@code getEntryCount},
2988       * {@code getSearchEntries}, {@code getReferenceCount}, and
2989       * {@code getSearchReferences} may be used to obtain information about those
2990       * entries and references (although if a search result listener was provided,
2991       * then it will have been used to make any entries and references available,
2992       * and they will not be available through the {@code getSearchEntries} and
2993       * {@code getSearchReferences} methods).
2994       *
2995       * @param  searchResultListener  The search result listener that should be
2996       *                               used to return results to the client.  It may
2997       *                               be {@code null} if the search results should
2998       *                               be collected internally and returned in the
2999       *                               {@code SearchResult} object.
3000       * @param  baseDN                The base DN for the search request.  It must
3001       *                               not be {@code null}.
3002       * @param  scope                 The scope that specifies the range of entries
3003       *                               that should be examined for the search.
3004       * @param  filter                The filter to use to identify matching
3005       *                               entries.  It must not be {@code null}.
3006       * @param  attributes            The set of attributes that should be returned
3007       *                               in matching entries.  It may be {@code null}
3008       *                               or empty if the default attribute set (all
3009       *                               user attributes) is to be requested.
3010       *
3011       * @return  A search result object that provides information about the
3012       *          processing of the search, potentially including the set of
3013       *          matching entries and search references returned by the server.
3014       *
3015       * @throws  LDAPSearchException  If the search does not complete successfully,
3016       *                               or if a problem is encountered while sending
3017       *                               the request or reading the response.  If one
3018       *                               or more entries or references were returned
3019       *                               before the failure was encountered, then the
3020       *                               {@code LDAPSearchException} object may be
3021       *                               examined to obtain information about those
3022       *                               entries and/or references.
3023       */
3024      public SearchResult search(final SearchResultListener searchResultListener,
3025                                 final String baseDN, final SearchScope scope,
3026                                 final Filter filter, final String... attributes)
3027             throws LDAPSearchException
3028      {
3029        ensureNotNull(baseDN, filter);
3030    
3031        try
3032        {
3033          return search(new SearchRequest(searchResultListener, baseDN, scope,
3034                                          filter, attributes));
3035        }
3036        catch (LDAPSearchException lse)
3037        {
3038          debugException(lse);
3039          throw lse;
3040        }
3041        catch (LDAPException le)
3042        {
3043          debugException(le);
3044          throw new LDAPSearchException(le);
3045        }
3046      }
3047    
3048    
3049    
3050      /**
3051       * Processes a search operation with the provided information.  The search
3052       * result entries and references will be collected internally and included in
3053       * the {@code SearchResult} object that is returned.
3054       * <BR><BR>
3055       * Note that if the search does not complete successfully, an
3056       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3057       * search result entries or references may have been returned before the
3058       * failure response is received.  In this case, the
3059       * {@code LDAPSearchException} methods like {@code getEntryCount},
3060       * {@code getSearchEntries}, {@code getReferenceCount}, and
3061       * {@code getSearchReferences} may be used to obtain information about those
3062       * entries and references.
3063       *
3064       * @param  baseDN       The base DN for the search request.  It must not be
3065       *                      {@code null}.
3066       * @param  scope        The scope that specifies the range of entries that
3067       *                      should be examined for the search.
3068       * @param  derefPolicy  The dereference policy the server should use for any
3069       *                      aliases encountered while processing the search.
3070       * @param  sizeLimit    The maximum number of entries that the server should
3071       *                      return for the search.  A value of zero indicates that
3072       *                      there should be no limit.
3073       * @param  timeLimit    The maximum length of time in seconds that the server
3074       *                      should spend processing this search request.  A value
3075       *                      of zero indicates that there should be no limit.
3076       * @param  typesOnly    Indicates whether to return only attribute names in
3077       *                      matching entries, or both attribute names and values.
3078       * @param  filter       The string representation of the filter to use to
3079       *                      identify matching entries.  It must not be
3080       *                      {@code null}.
3081       * @param  attributes   The set of attributes that should be returned in
3082       *                      matching entries.  It may be {@code null} or empty if
3083       *                      the default attribute set (all user attributes) is to
3084       *                      be requested.
3085       *
3086       * @return  A search result object that provides information about the
3087       *          processing of the search, including the set of matching entries
3088       *          and search references returned by the server.
3089       *
3090       * @throws  LDAPSearchException  If the search does not complete successfully,
3091       *                               or if a problem is encountered while parsing
3092       *                               the provided filter string, sending the
3093       *                               request, or reading the response.  If one
3094       *                               or more entries or references were returned
3095       *                               before the failure was encountered, then the
3096       *                               {@code LDAPSearchException} object may be
3097       *                               examined to obtain information about those
3098       *                               entries and/or references.
3099       */
3100      public SearchResult search(final String baseDN, final SearchScope scope,
3101                                 final DereferencePolicy derefPolicy,
3102                                 final int sizeLimit, final int timeLimit,
3103                                 final boolean typesOnly, final String filter,
3104                                 final String... attributes)
3105             throws LDAPSearchException
3106      {
3107        ensureNotNull(baseDN, filter);
3108    
3109        try
3110        {
3111          return search(new SearchRequest(baseDN, scope, derefPolicy, sizeLimit,
3112                                          timeLimit, typesOnly, filter,
3113                                          attributes));
3114        }
3115        catch (LDAPSearchException lse)
3116        {
3117          debugException(lse);
3118          throw lse;
3119        }
3120        catch (LDAPException le)
3121        {
3122          debugException(le);
3123          throw new LDAPSearchException(le);
3124        }
3125      }
3126    
3127    
3128    
3129      /**
3130       * Processes a search operation with the provided information.  The search
3131       * result entries and references will be collected internally and included in
3132       * the {@code SearchResult} object that is returned.
3133       * <BR><BR>
3134       * Note that if the search does not complete successfully, an
3135       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3136       * search result entries or references may have been returned before the
3137       * failure response is received.  In this case, the
3138       * {@code LDAPSearchException} methods like {@code getEntryCount},
3139       * {@code getSearchEntries}, {@code getReferenceCount}, and
3140       * {@code getSearchReferences} may be used to obtain information about those
3141       * entries and references.
3142       *
3143       * @param  baseDN       The base DN for the search request.  It must not be
3144       *                      {@code null}.
3145       * @param  scope        The scope that specifies the range of entries that
3146       *                      should be examined for the search.
3147       * @param  derefPolicy  The dereference policy the server should use for any
3148       *                      aliases encountered while processing the search.
3149       * @param  sizeLimit    The maximum number of entries that the server should
3150       *                      return for the search.  A value of zero indicates that
3151       *                      there should be no limit.
3152       * @param  timeLimit    The maximum length of time in seconds that the server
3153       *                      should spend processing this search request.  A value
3154       *                      of zero indicates that there should be no limit.
3155       * @param  typesOnly    Indicates whether to return only attribute names in
3156       *                      matching entries, or both attribute names and values.
3157       * @param  filter       The filter to use to identify matching entries.  It
3158       *                      must not be {@code null}.
3159       * @param  attributes   The set of attributes that should be returned in
3160       *                      matching entries.  It may be {@code null} or empty if
3161       *                      the default attribute set (all user attributes) is to
3162       *                      be requested.
3163       *
3164       * @return  A search result object that provides information about the
3165       *          processing of the search, including the set of matching entries
3166       *          and search references returned by the server.
3167       *
3168       * @throws  LDAPSearchException  If the search does not complete successfully,
3169       *                               or if a problem is encountered while sending
3170       *                               the request or reading the response.  If one
3171       *                               or more entries or references were returned
3172       *                               before the failure was encountered, then the
3173       *                               {@code LDAPSearchException} object may be
3174       *                               examined to obtain information about those
3175       *                               entries and/or references.
3176       */
3177      public SearchResult search(final String baseDN, final SearchScope scope,
3178                                 final DereferencePolicy derefPolicy,
3179                                 final int sizeLimit, final int timeLimit,
3180                                 final boolean typesOnly, final Filter filter,
3181                                 final String... attributes)
3182             throws LDAPSearchException
3183      {
3184        ensureNotNull(baseDN, filter);
3185    
3186        return search(new SearchRequest(baseDN, scope, derefPolicy, sizeLimit,
3187                                        timeLimit, typesOnly, filter, attributes));
3188      }
3189    
3190    
3191    
3192      /**
3193       * Processes a search operation with the provided information.
3194       * <BR><BR>
3195       * Note that if the search does not complete successfully, an
3196       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3197       * search result entries or references may have been returned before the
3198       * failure response is received.  In this case, the
3199       * {@code LDAPSearchException} methods like {@code getEntryCount},
3200       * {@code getSearchEntries}, {@code getReferenceCount}, and
3201       * {@code getSearchReferences} may be used to obtain information about those
3202       * entries and references (although if a search result listener was provided,
3203       * then it will have been used to make any entries and references available,
3204       * and they will not be available through the {@code getSearchEntries} and
3205       * {@code getSearchReferences} methods).
3206       *
3207       * @param  searchResultListener  The search result listener that should be
3208       *                               used to return results to the client.  It may
3209       *                               be {@code null} if the search results should
3210       *                               be collected internally and returned in the
3211       *                               {@code SearchResult} object.
3212       * @param  baseDN                The base DN for the search request.  It must
3213       *                               not be {@code null}.
3214       * @param  scope                 The scope that specifies the range of entries
3215       *                               that should be examined for the search.
3216       * @param  derefPolicy           The dereference policy the server should use
3217       *                               for any aliases encountered while processing
3218       *                               the search.
3219       * @param  sizeLimit             The maximum number of entries that the server
3220       *                               should return for the search.  A value of
3221       *                               zero indicates that there should be no limit.
3222       * @param  timeLimit             The maximum length of time in seconds that
3223       *                               the server should spend processing this
3224       *                               search request.  A value of zero indicates
3225       *                               that there should be no limit.
3226       * @param  typesOnly             Indicates whether to return only attribute
3227       *                               names in matching entries, or both attribute
3228       *                               names and values.
3229       * @param  filter                The string representation of the filter to
3230       *                               use to identify matching entries.  It must
3231       *                               not be {@code null}.
3232       * @param  attributes            The set of attributes that should be returned
3233       *                               in matching entries.  It may be {@code null}
3234       *                               or empty if the default attribute set (all
3235       *                               user attributes) is to be requested.
3236       *
3237       * @return  A search result object that provides information about the
3238       *          processing of the search, potentially including the set of
3239       *          matching entries and search references returned by the server.
3240       *
3241       * @throws  LDAPSearchException  If the search does not complete successfully,
3242       *                               or if a problem is encountered while parsing
3243       *                               the provided filter string, sending the
3244       *                               request, or reading the response.  If one
3245       *                               or more entries or references were returned
3246       *                               before the failure was encountered, then the
3247       *                               {@code LDAPSearchException} object may be
3248       *                               examined to obtain information about those
3249       *                               entries and/or references.
3250       */
3251      public SearchResult search(final SearchResultListener searchResultListener,
3252                                 final String baseDN, final SearchScope scope,
3253                                 final DereferencePolicy derefPolicy,
3254                                 final int sizeLimit, final int timeLimit,
3255                                 final boolean typesOnly, final String filter,
3256                                 final String... attributes)
3257             throws LDAPSearchException
3258      {
3259        ensureNotNull(baseDN, filter);
3260    
3261        try
3262        {
3263          return search(new SearchRequest(searchResultListener, baseDN, scope,
3264                                          derefPolicy, sizeLimit, timeLimit,
3265                                          typesOnly, filter, attributes));
3266        }
3267        catch (LDAPSearchException lse)
3268        {
3269          debugException(lse);
3270          throw lse;
3271        }
3272        catch (LDAPException le)
3273        {
3274          debugException(le);
3275          throw new LDAPSearchException(le);
3276        }
3277      }
3278    
3279    
3280    
3281      /**
3282       * Processes a search operation with the provided information.
3283       * <BR><BR>
3284       * Note that if the search does not complete successfully, an
3285       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3286       * search result entries or references may have been returned before the
3287       * failure response is received.  In this case, the
3288       * {@code LDAPSearchException} methods like {@code getEntryCount},
3289       * {@code getSearchEntries}, {@code getReferenceCount}, and
3290       * {@code getSearchReferences} may be used to obtain information about those
3291       * entries and references (although if a search result listener was provided,
3292       * then it will have been used to make any entries and references available,
3293       * and they will not be available through the {@code getSearchEntries} and
3294       * {@code getSearchReferences} methods).
3295       *
3296       * @param  searchResultListener  The search result listener that should be
3297       *                               used to return results to the client.  It may
3298       *                               be {@code null} if the search results should
3299       *                               be collected internally and returned in the
3300       *                               {@code SearchResult} object.
3301       * @param  baseDN                The base DN for the search request.  It must
3302       *                               not be {@code null}.
3303       * @param  scope                 The scope that specifies the range of entries
3304       *                               that should be examined for the search.
3305       * @param  derefPolicy           The dereference policy the server should use
3306       *                               for any aliases encountered while processing
3307       *                               the search.
3308       * @param  sizeLimit             The maximum number of entries that the server
3309       *                               should return for the search.  A value of
3310       *                               zero indicates that there should be no limit.
3311       * @param  timeLimit             The maximum length of time in seconds that
3312       *                               the server should spend processing this
3313       *                               search request.  A value of zero indicates
3314       *                               that there should be no limit.
3315       * @param  typesOnly             Indicates whether to return only attribute
3316       *                               names in matching entries, or both attribute
3317       *                               names and values.
3318       * @param  filter                The filter to use to identify matching
3319       *                               entries.  It must not be {@code null}.
3320       * @param  attributes            The set of attributes that should be returned
3321       *                               in matching entries.  It may be {@code null}
3322       *                               or empty if the default attribute set (all
3323       *                               user attributes) is to be requested.
3324       *
3325       * @return  A search result object that provides information about the
3326       *          processing of the search, potentially including the set of
3327       *          matching entries and search references returned by the server.
3328       *
3329       * @throws  LDAPSearchException  If the search does not complete successfully,
3330       *                               or if a problem is encountered while sending
3331       *                               the request or reading the response.  If one
3332       *                               or more entries or references were returned
3333       *                               before the failure was encountered, then the
3334       *                               {@code LDAPSearchException} object may be
3335       *                               examined to obtain information about those
3336       *                               entries and/or references.
3337       */
3338      public SearchResult search(final SearchResultListener searchResultListener,
3339                                 final String baseDN, final SearchScope scope,
3340                                 final DereferencePolicy derefPolicy,
3341                                 final int sizeLimit, final int timeLimit,
3342                                 final boolean typesOnly, final Filter filter,
3343                                 final String... attributes)
3344             throws LDAPSearchException
3345      {
3346        ensureNotNull(baseDN, filter);
3347    
3348        return search(new SearchRequest(searchResultListener, baseDN, scope,
3349                                        derefPolicy, sizeLimit, timeLimit,
3350                                        typesOnly, filter, attributes));
3351      }
3352    
3353    
3354    
3355      /**
3356       * Processes the provided search request.
3357       * <BR><BR>
3358       * Note that if the search does not complete successfully, an
3359       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3360       * search result entries or references may have been returned before the
3361       * failure response is received.  In this case, the
3362       * {@code LDAPSearchException} methods like {@code getEntryCount},
3363       * {@code getSearchEntries}, {@code getReferenceCount}, and
3364       * {@code getSearchReferences} may be used to obtain information about those
3365       * entries and references (although if a search result listener was provided,
3366       * then it will have been used to make any entries and references available,
3367       * and they will not be available through the {@code getSearchEntries} and
3368       * {@code getSearchReferences} methods).
3369       *
3370       * @param  searchRequest  The search request to be processed.  It must not be
3371       *                        {@code null}.
3372       *
3373       * @return  A search result object that provides information about the
3374       *          processing of the search, potentially including the set of
3375       *          matching entries and search references returned by the server.
3376       *
3377       * @throws  LDAPSearchException  If the search does not complete successfully,
3378       *                               or if a problem is encountered while sending
3379       *                               the request or reading the response.  If one
3380       *                               or more entries or references were returned
3381       *                               before the failure was encountered, then the
3382       *                               {@code LDAPSearchException} object may be
3383       *                               examined to obtain information about those
3384       *                               entries and/or references.
3385       */
3386      public SearchResult search(final SearchRequest searchRequest)
3387             throws LDAPSearchException
3388      {
3389        ensureNotNull(searchRequest);
3390    
3391        final SearchResult searchResult;
3392        try
3393        {
3394          searchResult = searchRequest.process(this, 1);
3395        }
3396        catch (LDAPSearchException lse)
3397        {
3398          debugException(lse);
3399          throw lse;
3400        }
3401        catch (LDAPException le)
3402        {
3403          debugException(le);
3404          throw new LDAPSearchException(le);
3405        }
3406    
3407        if (! searchResult.getResultCode().equals(ResultCode.SUCCESS))
3408        {
3409          throw new LDAPSearchException(searchResult);
3410        }
3411    
3412        return searchResult;
3413      }
3414    
3415    
3416    
3417      /**
3418       * Processes the provided search request.
3419       * <BR><BR>
3420       * Note that if the search does not complete successfully, an
3421       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3422       * search result entries or references may have been returned before the
3423       * failure response is received.  In this case, the
3424       * {@code LDAPSearchException} methods like {@code getEntryCount},
3425       * {@code getSearchEntries}, {@code getReferenceCount}, and
3426       * {@code getSearchReferences} may be used to obtain information about those
3427       * entries and references (although if a search result listener was provided,
3428       * then it will have been used to make any entries and references available,
3429       * and they will not be available through the {@code getSearchEntries} and
3430       * {@code getSearchReferences} methods).
3431       *
3432       * @param  searchRequest  The search request to be processed.  It must not be
3433       *                        {@code null}.
3434       *
3435       * @return  A search result object that provides information about the
3436       *          processing of the search, potentially including the set of
3437       *          matching entries and search references returned by the server.
3438       *
3439       * @throws  LDAPSearchException  If the search does not complete successfully,
3440       *                               or if a problem is encountered while sending
3441       *                               the request or reading the response.  If one
3442       *                               or more entries or references were returned
3443       *                               before the failure was encountered, then the
3444       *                               {@code LDAPSearchException} object may be
3445       *                               examined to obtain information about those
3446       *                               entries and/or references.
3447       */
3448      public SearchResult search(final ReadOnlySearchRequest searchRequest)
3449             throws LDAPSearchException
3450      {
3451        return search((SearchRequest) searchRequest);
3452      }
3453    
3454    
3455    
3456      /**
3457       * Processes a search operation with the provided information.  It is expected
3458       * that at most one entry will be returned from the search, and that no
3459       * additional content from the successful search result (e.g., diagnostic
3460       * message or response controls) are needed.
3461       * <BR><BR>
3462       * Note that if the search does not complete successfully, an
3463       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3464       * search result entries or references may have been returned before the
3465       * failure response is received.  In this case, the
3466       * {@code LDAPSearchException} methods like {@code getEntryCount},
3467       * {@code getSearchEntries}, {@code getReferenceCount}, and
3468       * {@code getSearchReferences} may be used to obtain information about those
3469       * entries and references.
3470       *
3471       * @param  baseDN      The base DN for the search request.  It must not be
3472       *                     {@code null}.
3473       * @param  scope       The scope that specifies the range of entries that
3474       *                     should be examined for the search.
3475       * @param  filter      The string representation of the filter to use to
3476       *                     identify matching entries.  It must not be
3477       *                     {@code null}.
3478       * @param  attributes  The set of attributes that should be returned in
3479       *                     matching entries.  It may be {@code null} or empty if
3480       *                     the default attribute set (all user attributes) is to
3481       *                     be requested.
3482       *
3483       * @return  The entry that was returned from the search, or {@code null} if no
3484       *          entry was returned or the base entry does not exist.
3485       *
3486       * @throws  LDAPSearchException  If the search does not complete successfully,
3487       *                               if more than a single entry is returned, or
3488       *                               if a problem is encountered while parsing the
3489       *                               provided filter string, sending the request,
3490       *                               or reading the response.  If one or more
3491       *                               entries or references were returned before
3492       *                               the failure was encountered, then the
3493       *                               {@code LDAPSearchException} object may be
3494       *                               examined to obtain information about those
3495       *                               entries and/or references.
3496       */
3497      public SearchResultEntry searchForEntry(final String baseDN,
3498                                              final SearchScope scope,
3499                                              final String filter,
3500                                              final String... attributes)
3501             throws LDAPSearchException
3502      {
3503        final SearchRequest r;
3504        try
3505        {
3506          r = new SearchRequest(baseDN, scope, DereferencePolicy.NEVER, 1, 0, false,
3507               filter, attributes);
3508        }
3509        catch (final LDAPException le)
3510        {
3511          debugException(le);
3512          throw new LDAPSearchException(le);
3513        }
3514    
3515        return searchForEntry(r);
3516      }
3517    
3518    
3519    
3520      /**
3521       * Processes a search operation with the provided information.  It is expected
3522       * that at most one entry will be returned from the search, and that no
3523       * additional content from the successful search result (e.g., diagnostic
3524       * message or response controls) are needed.
3525       * <BR><BR>
3526       * Note that if the search does not complete successfully, an
3527       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3528       * search result entries or references may have been returned before the
3529       * failure response is received.  In this case, the
3530       * {@code LDAPSearchException} methods like {@code getEntryCount},
3531       * {@code getSearchEntries}, {@code getReferenceCount}, and
3532       * {@code getSearchReferences} may be used to obtain information about those
3533       * entries and references.
3534       *
3535       * @param  baseDN      The base DN for the search request.  It must not be
3536       *                     {@code null}.
3537       * @param  scope       The scope that specifies the range of entries that
3538       *                     should be examined for the search.
3539       * @param  filter      The string representation of the filter to use to
3540       *                     identify matching entries.  It must not be
3541       *                     {@code null}.
3542       * @param  attributes  The set of attributes that should be returned in
3543       *                     matching entries.  It may be {@code null} or empty if
3544       *                     the default attribute set (all user attributes) is to
3545       *                     be requested.
3546       *
3547       * @return  The entry that was returned from the search, or {@code null} if no
3548       *          entry was returned or the base entry does not exist.
3549       *
3550       * @throws  LDAPSearchException  If the search does not complete successfully,
3551       *                               if more than a single entry is returned, or
3552       *                               if a problem is encountered while parsing the
3553       *                               provided filter string, sending the request,
3554       *                               or reading the response.  If one or more
3555       *                               entries or references were returned before
3556       *                               the failure was encountered, then the
3557       *                               {@code LDAPSearchException} object may be
3558       *                               examined to obtain information about those
3559       *                               entries and/or references.
3560       */
3561      public SearchResultEntry searchForEntry(final String baseDN,
3562                                              final SearchScope scope,
3563                                              final Filter filter,
3564                                              final String... attributes)
3565             throws LDAPSearchException
3566      {
3567        return searchForEntry(new SearchRequest(baseDN, scope,
3568             DereferencePolicy.NEVER, 1, 0, false, filter, attributes));
3569      }
3570    
3571    
3572    
3573      /**
3574       * Processes a search operation with the provided information.  It is expected
3575       * that at most one entry will be returned from the search, and that no
3576       * additional content from the successful search result (e.g., diagnostic
3577       * message or response controls) are needed.
3578       * <BR><BR>
3579       * Note that if the search does not complete successfully, an
3580       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3581       * search result entries or references may have been returned before the
3582       * failure response is received.  In this case, the
3583       * {@code LDAPSearchException} methods like {@code getEntryCount},
3584       * {@code getSearchEntries}, {@code getReferenceCount}, and
3585       * {@code getSearchReferences} may be used to obtain information about those
3586       * entries and references.
3587       *
3588       * @param  baseDN       The base DN for the search request.  It must not be
3589       *                      {@code null}.
3590       * @param  scope        The scope that specifies the range of entries that
3591       *                      should be examined for the search.
3592       * @param  derefPolicy  The dereference policy the server should use for any
3593       *                      aliases encountered while processing the search.
3594       * @param  timeLimit    The maximum length of time in seconds that the server
3595       *                      should spend processing this search request.  A value
3596       *                      of zero indicates that there should be no limit.
3597       * @param  typesOnly    Indicates whether to return only attribute names in
3598       *                      matching entries, or both attribute names and values.
3599       * @param  filter       The string representation of the filter to use to
3600       *                      identify matching entries.  It must not be
3601       *                      {@code null}.
3602       * @param  attributes   The set of attributes that should be returned in
3603       *                      matching entries.  It may be {@code null} or empty if
3604       *                      the default attribute set (all user attributes) is to
3605       *                      be requested.
3606       *
3607       * @return  The entry that was returned from the search, or {@code null} if no
3608       *          entry was returned or the base entry does not exist.
3609       *
3610       * @throws  LDAPSearchException  If the search does not complete successfully,
3611       *                               if more than a single entry is returned, or
3612       *                               if a problem is encountered while parsing the
3613       *                               provided filter string, sending the request,
3614       *                               or reading the response.  If one or more
3615       *                               entries or references were returned before
3616       *                               the failure was encountered, then the
3617       *                               {@code LDAPSearchException} object may be
3618       *                               examined to obtain information about those
3619       *                               entries and/or references.
3620       */
3621      public SearchResultEntry searchForEntry(final String baseDN,
3622                                              final SearchScope scope,
3623                                              final DereferencePolicy derefPolicy,
3624                                              final int timeLimit,
3625                                              final boolean typesOnly,
3626                                              final String filter,
3627                                              final String... attributes)
3628             throws LDAPSearchException
3629      {
3630        final SearchRequest r;
3631        try
3632        {
3633          r = new SearchRequest(baseDN, scope, derefPolicy, 1, timeLimit, typesOnly,
3634               filter, attributes);
3635        }
3636        catch (final LDAPException le)
3637        {
3638          debugException(le);
3639          throw new LDAPSearchException(le);
3640        }
3641    
3642        return searchForEntry(r);
3643      }
3644    
3645    
3646    
3647      /**
3648       * Processes a search operation with the provided information.  It is expected
3649       * that at most one entry will be returned from the search, and that no
3650       * additional content from the successful search result (e.g., diagnostic
3651       * message or response controls) are needed.
3652       * <BR><BR>
3653       * Note that if the search does not complete successfully, an
3654       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3655       * search result entries or references may have been returned before the
3656       * failure response is received.  In this case, the
3657       * {@code LDAPSearchException} methods like {@code getEntryCount},
3658       * {@code getSearchEntries}, {@code getReferenceCount}, and
3659       * {@code getSearchReferences} may be used to obtain information about those
3660       * entries and references.
3661       *
3662       * @param  baseDN       The base DN for the search request.  It must not be
3663       *                      {@code null}.
3664       * @param  scope        The scope that specifies the range of entries that
3665       *                      should be examined for the search.
3666       * @param  derefPolicy  The dereference policy the server should use for any
3667       *                      aliases encountered while processing the search.
3668       * @param  timeLimit    The maximum length of time in seconds that the server
3669       *                      should spend processing this search request.  A value
3670       *                      of zero indicates that there should be no limit.
3671       * @param  typesOnly    Indicates whether to return only attribute names in
3672       *                      matching entries, or both attribute names and values.
3673       * @param  filter       The filter to use to identify matching entries.  It
3674       *                      must not be {@code null}.
3675       * @param  attributes   The set of attributes that should be returned in
3676       *                      matching entries.  It may be {@code null} or empty if
3677       *                      the default attribute set (all user attributes) is to
3678       *                      be requested.
3679       *
3680       * @return  The entry that was returned from the search, or {@code null} if no
3681       *          entry was returned or the base entry does not exist.
3682       *
3683       * @throws  LDAPSearchException  If the search does not complete successfully,
3684       *                               if more than a single entry is returned, or
3685       *                               if a problem is encountered while parsing the
3686       *                               provided filter string, sending the request,
3687       *                               or reading the response.  If one or more
3688       *                               entries or references were returned before
3689       *                               the failure was encountered, then the
3690       *                               {@code LDAPSearchException} object may be
3691       *                               examined to obtain information about those
3692       *                               entries and/or references.
3693       */
3694      public SearchResultEntry searchForEntry(final String baseDN,
3695                                              final SearchScope scope,
3696                                              final DereferencePolicy derefPolicy,
3697                                              final int timeLimit,
3698                                              final boolean typesOnly,
3699                                              final Filter filter,
3700                                              final String... attributes)
3701           throws LDAPSearchException
3702      {
3703        return searchForEntry(new SearchRequest(baseDN, scope, derefPolicy, 1,
3704             timeLimit, typesOnly, filter, attributes));
3705      }
3706    
3707    
3708    
3709      /**
3710       * Processes the provided search request.  It is expected that at most one
3711       * entry will be returned from the search, and that no additional content from
3712       * the successful search result (e.g., diagnostic message or response
3713       * controls) are needed.
3714       * <BR><BR>
3715       * Note that if the search does not complete successfully, an
3716       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3717       * search result entries or references may have been returned before the
3718       * failure response is received.  In this case, the
3719       * {@code LDAPSearchException} methods like {@code getEntryCount},
3720       * {@code getSearchEntries}, {@code getReferenceCount}, and
3721       * {@code getSearchReferences} may be used to obtain information about those
3722       * entries and references.
3723       *
3724       * @param  searchRequest  The search request to be processed.  If it is
3725       *                        configured with a search result listener or a size
3726       *                        limit other than one, then the provided request will
3727       *                        be duplicated with the appropriate settings.
3728       *
3729       * @return  The entry that was returned from the search, or {@code null} if no
3730       *          entry was returned or the base entry does not exist.
3731       *
3732       * @throws  LDAPSearchException  If the search does not complete successfully,
3733       *                               if more than a single entry is returned, or
3734       *                               if a problem is encountered while parsing the
3735       *                               provided filter string, sending the request,
3736       *                               or reading the response.  If one or more
3737       *                               entries or references were returned before
3738       *                               the failure was encountered, then the
3739       *                               {@code LDAPSearchException} object may be
3740       *                               examined to obtain information about those
3741       *                               entries and/or references.
3742       */
3743      public SearchResultEntry searchForEntry(final SearchRequest searchRequest)
3744             throws LDAPSearchException
3745      {
3746        final SearchRequest r;
3747        if ((searchRequest.getSearchResultListener() != null) ||
3748            (searchRequest.getSizeLimit() != 1))
3749        {
3750          r = new SearchRequest(searchRequest.getBaseDN(), searchRequest.getScope(),
3751               searchRequest.getDereferencePolicy(), 1,
3752               searchRequest.getTimeLimitSeconds(), searchRequest.typesOnly(),
3753               searchRequest.getFilter(), searchRequest.getAttributes());
3754    
3755          r.setFollowReferrals(searchRequest.followReferralsInternal());
3756          r.setResponseTimeoutMillis(searchRequest.getResponseTimeoutMillis(null));
3757    
3758          if (searchRequest.hasControl())
3759          {
3760            r.setControlsInternal(searchRequest.getControls());
3761          }
3762        }
3763        else
3764        {
3765          r = searchRequest;
3766        }
3767    
3768        final SearchResult result;
3769        try
3770        {
3771          result = search(r);
3772        }
3773        catch (final LDAPSearchException lse)
3774        {
3775          debugException(lse);
3776    
3777          if (lse.getResultCode() == ResultCode.NO_SUCH_OBJECT)
3778          {
3779            return null;
3780          }
3781    
3782          throw lse;
3783        }
3784    
3785        if (result.getEntryCount() == 0)
3786        {
3787          return null;
3788        }
3789        else
3790        {
3791          return result.getSearchEntries().get(0);
3792        }
3793      }
3794    
3795    
3796    
3797      /**
3798       * Processes the provided search request.  It is expected that at most one
3799       * entry will be returned from the search, and that no additional content from
3800       * the successful search result (e.g., diagnostic message or response
3801       * controls) are needed.
3802       * <BR><BR>
3803       * Note that if the search does not complete successfully, an
3804       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3805       * search result entries or references may have been returned before the
3806       * failure response is received.  In this case, the
3807       * {@code LDAPSearchException} methods like {@code getEntryCount},
3808       * {@code getSearchEntries}, {@code getReferenceCount}, and
3809       * {@code getSearchReferences} may be used to obtain information about those
3810       * entries and references.
3811       *
3812       * @param  searchRequest  The search request to be processed.  If it is
3813       *                        configured with a search result listener or a size
3814       *                        limit other than one, then the provided request will
3815       *                        be duplicated with the appropriate settings.
3816       *
3817       * @return  The entry that was returned from the search, or {@code null} if no
3818       *          entry was returned or the base entry does not exist.
3819       *
3820       * @throws  LDAPSearchException  If the search does not complete successfully,
3821       *                               if more than a single entry is returned, or
3822       *                               if a problem is encountered while parsing the
3823       *                               provided filter string, sending the request,
3824       *                               or reading the response.  If one or more
3825       *                               entries or references were returned before
3826       *                               the failure was encountered, then the
3827       *                               {@code LDAPSearchException} object may be
3828       *                               examined to obtain information about those
3829       *                               entries and/or references.
3830       */
3831      public SearchResultEntry searchForEntry(
3832                                    final ReadOnlySearchRequest searchRequest)
3833             throws LDAPSearchException
3834      {
3835        return searchForEntry((SearchRequest) searchRequest);
3836      }
3837    
3838    
3839    
3840      /**
3841       * Processes the provided search request as an asynchronous operation.
3842       *
3843       * @param  searchRequest  The search request to be processed.  It must not be
3844       *                        {@code null}, and it must be configured with a
3845       *                        search result listener that is also an
3846       *                        {@code AsyncSearchResultListener}.
3847       *
3848       * @return  An async request ID that may be used to reference the operation.
3849       *
3850       * @throws  LDAPException  If the provided search request does not have a
3851       *                         search result listener that is an
3852       *                         {@code AsyncSearchResultListener}, or if a problem
3853       *                         occurs while sending the request.
3854       */
3855      public AsyncRequestID asyncSearch(final SearchRequest searchRequest)
3856             throws LDAPException
3857      {
3858        ensureNotNull(searchRequest);
3859    
3860        final SearchResultListener searchListener =
3861             searchRequest.getSearchResultListener();
3862        if (searchListener == null)
3863        {
3864          final LDAPException le = new LDAPException(ResultCode.PARAM_ERROR,
3865               ERR_ASYNC_SEARCH_NO_LISTENER.get());
3866          debugCodingError(le);
3867          throw le;
3868        }
3869        else if (! (searchListener instanceof AsyncSearchResultListener))
3870        {
3871          final LDAPException le = new LDAPException(ResultCode.PARAM_ERROR,
3872               ERR_ASYNC_SEARCH_INVALID_LISTENER.get());
3873          debugCodingError(le);
3874          throw le;
3875        }
3876    
3877        if (synchronousMode())
3878        {
3879          throw new LDAPException(ResultCode.NOT_SUPPORTED,
3880               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
3881        }
3882    
3883        return searchRequest.processAsync(this,
3884             (AsyncSearchResultListener) searchListener);
3885      }
3886    
3887    
3888    
3889      /**
3890       * Processes the provided search request as an asynchronous operation.
3891       *
3892       * @param  searchRequest  The search request to be processed.  It must not be
3893       *                        {@code null}, and it must be configured with a
3894       *                        search result listener that is also an
3895       *                        {@code AsyncSearchResultListener}.
3896       *
3897       * @return  An async request ID that may be used to reference the operation.
3898       *
3899       * @throws  LDAPException  If the provided search request does not have a
3900       *                         search result listener that is an
3901       *                         {@code AsyncSearchResultListener}, or if a problem
3902       *                         occurs while sending the request.
3903       */
3904      public AsyncRequestID asyncSearch(final ReadOnlySearchRequest searchRequest)
3905             throws LDAPException
3906      {
3907        if (synchronousMode())
3908        {
3909          throw new LDAPException(ResultCode.NOT_SUPPORTED,
3910               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
3911        }
3912    
3913        return asyncSearch((SearchRequest) searchRequest);
3914      }
3915    
3916    
3917    
3918      /**
3919       * Processes the provided generic request and returns the result.  This may
3920       * be useful for cases in which it is not known what type of operation the
3921       * request represents.
3922       *
3923       * @param  request  The request to be processed.
3924       *
3925       * @return  The result obtained from processing the request.
3926       *
3927       * @throws  LDAPException  If a problem occurs while sending the request or
3928       *                         reading the response.  Note simply having a
3929       *                         non-success result code in the response will not
3930       *                         cause an exception to be thrown.
3931       */
3932      public LDAPResult processOperation(final LDAPRequest request)
3933             throws LDAPException
3934      {
3935        return request.process(this, 1);
3936      }
3937    
3938    
3939    
3940      /**
3941       * Retrieves the referral connector that should be used to establish
3942       * connections for use when following referrals.
3943       *
3944       * @return  The referral connector that should be used to establish
3945       *          connections for use when following referrals.
3946       */
3947      public ReferralConnector getReferralConnector()
3948      {
3949        if (referralConnector == null)
3950        {
3951          return this;
3952        }
3953        else
3954        {
3955          return referralConnector;
3956        }
3957      }
3958    
3959    
3960    
3961      /**
3962       * Specifies the referral connector that should be used to establish
3963       * connections for use when following referrals.
3964       *
3965       * @param  referralConnector  The referral connector that should be used to
3966       *                            establish connections for use when following
3967       *                            referrals.
3968       */
3969      public void setReferralConnector(final ReferralConnector referralConnector)
3970      {
3971        if (referralConnector == null)
3972        {
3973          this.referralConnector = this;
3974        }
3975        else
3976        {
3977          this.referralConnector = referralConnector;
3978        }
3979      }
3980    
3981    
3982    
3983      /**
3984       * Sends the provided LDAP message to the server over this connection.
3985       *
3986       * @param  message  The LDAP message to send to the target server.
3987       *
3988       * @throws  LDAPException  If a problem occurs while sending the request.
3989       */
3990      void sendMessage(final LDAPMessage message)
3991             throws LDAPException
3992      {
3993        if (needsReconnect.compareAndSet(true, false))
3994        {
3995          reconnect();
3996        }
3997    
3998        final LDAPConnectionInternals internals = connectionInternals;
3999        if (internals == null)
4000        {
4001          throw new LDAPException(ResultCode.SERVER_DOWN,
4002                                  ERR_CONN_NOT_ESTABLISHED.get());
4003        }
4004        else
4005        {
4006          internals.sendMessage(message, connectionOptions.autoReconnect());
4007          lastCommunicationTime = System.currentTimeMillis();
4008        }
4009      }
4010    
4011    
4012    
4013      /**
4014       * Retrieves the message ID that should be used for the next request sent
4015       * over this connection.
4016       *
4017       * @return  The message ID that should be used for the next request sent over
4018       *          this connection, or -1 if this connection is not established.
4019       */
4020      int nextMessageID()
4021      {
4022        final LDAPConnectionInternals internals = connectionInternals;
4023        if (internals == null)
4024        {
4025          return -1;
4026        }
4027        else
4028        {
4029          return internals.nextMessageID();
4030        }
4031      }
4032    
4033    
4034    
4035      /**
4036       * Retrieves the disconnect info object for this connection, if available.
4037       *
4038       * @return  The disconnect info for this connection, or {@code null} if none
4039       *          is set.
4040       */
4041      DisconnectInfo getDisconnectInfo()
4042      {
4043        return disconnectInfo.get();
4044      }
4045    
4046    
4047    
4048      /**
4049       * Sets the disconnect type, message, and cause for this connection, if those
4050       * values have not been previously set.  It will not overwrite any values that
4051       * had been previously set.
4052       * <BR><BR>
4053       * This method may be called by code which is not part of the LDAP SDK to
4054       * provide additional information about the reason for the closure.  In that
4055       * case, this method must be called before the call to
4056       * {@link LDAPConnection#close}.
4057       *
4058       * @param  type     The disconnect type.  It must not be {@code null}.
4059       * @param  message  A message providing additional information about the
4060       *                  disconnect.  It may be {@code null} if no message is
4061       *                  available.
4062       * @param  cause    The exception that was caught to trigger the disconnect.
4063       *                  It may be {@code null} if the disconnect was not triggered
4064       *                  by an exception.
4065       */
4066      public void setDisconnectInfo(final DisconnectType type, final String message,
4067                                    final Throwable cause)
4068      {
4069        disconnectInfo.compareAndSet(null,
4070             new DisconnectInfo(this, type, message, cause));
4071      }
4072    
4073    
4074    
4075      /**
4076       * Sets the disconnect info for this connection, if it is not already set.
4077       *
4078       * @param  info  The disconnect info to be set, if it is not already set.
4079       *
4080       * @return  The disconnect info set for the connection, whether it was
4081       *          previously or newly set.
4082       */
4083      DisconnectInfo setDisconnectInfo(final DisconnectInfo info)
4084      {
4085        disconnectInfo.compareAndSet(null, info);
4086        return disconnectInfo.get();
4087      }
4088    
4089    
4090    
4091      /**
4092       * Retrieves the disconnect type for this connection, if available.
4093       *
4094       * @return  The disconnect type for this connection, or {@code null} if no
4095       *          disconnect type has been set.
4096       */
4097      public DisconnectType getDisconnectType()
4098      {
4099        final DisconnectInfo di = disconnectInfo.get();
4100        if (di == null)
4101        {
4102          return null;
4103        }
4104        else
4105        {
4106          return di.getType();
4107        }
4108      }
4109    
4110    
4111    
4112      /**
4113       * Retrieves the disconnect message for this connection, which may provide
4114       * additional information about the reason for the disconnect, if available.
4115       *
4116       * @return  The disconnect message for this connection, or {@code null} if
4117       *          no disconnect message has been set.
4118       */
4119      public String getDisconnectMessage()
4120      {
4121        final DisconnectInfo di = disconnectInfo.get();
4122        if (di == null)
4123        {
4124          return null;
4125        }
4126        else
4127        {
4128          return di.getMessage();
4129        }
4130      }
4131    
4132    
4133    
4134      /**
4135       * Retrieves the disconnect cause for this connection, which is an exception
4136       * or error that triggered the connection termination, if available.
4137       *
4138       * @return  The disconnect cause for this connection, or {@code null} if no
4139       *          disconnect cause has been set.
4140       */
4141      public Throwable getDisconnectCause()
4142      {
4143        final DisconnectInfo di = disconnectInfo.get();
4144        if (di == null)
4145        {
4146          return null;
4147        }
4148        else
4149        {
4150          return di.getCause();
4151        }
4152      }
4153    
4154    
4155    
4156      /**
4157       * Indicates that this connection has been closed and is no longer available
4158       * for use.
4159       */
4160      void setClosed()
4161      {
4162        needsReconnect.set(false);
4163    
4164        if (disconnectInfo.get() == null)
4165        {
4166          try
4167          {
4168            final StackTraceElement[] stackElements =
4169                 Thread.currentThread().getStackTrace();
4170            final StackTraceElement[] parentStackElements =
4171                 new StackTraceElement[stackElements.length - 1];
4172            System.arraycopy(stackElements, 1, parentStackElements, 0,
4173                 parentStackElements.length);
4174    
4175            setDisconnectInfo(DisconnectType.OTHER,
4176                 ERR_CONN_CLOSED_BY_UNEXPECTED_CALL_PATH.get(
4177                      getStackTrace(parentStackElements)),
4178                 null);
4179          }
4180          catch (final Exception e)
4181          {
4182            debugException(e);
4183          }
4184        }
4185    
4186        connectionStatistics.incrementNumDisconnects();
4187        final LDAPConnectionInternals internals = connectionInternals;
4188        if (internals != null)
4189        {
4190          internals.close();
4191          connectionInternals = null;
4192        }
4193    
4194        cachedSchema = null;
4195        lastCommunicationTime = -1L;
4196    
4197        if (timer != null)
4198        {
4199          timer.cancel();
4200          timer = null;
4201        }
4202      }
4203    
4204    
4205    
4206      /**
4207       * Registers the provided response acceptor with the connection reader.
4208       *
4209       * @param  messageID         The message ID for which the acceptor is to be
4210       *                           registered.
4211       * @param  responseAcceptor  The response acceptor to register.
4212       *
4213       * @throws  LDAPException  If another message acceptor is already registered
4214       *                         with the provided message ID.
4215       */
4216      void registerResponseAcceptor(final int messageID,
4217                                    final ResponseAcceptor responseAcceptor)
4218           throws LDAPException
4219      {
4220        if (needsReconnect.compareAndSet(true, false))
4221        {
4222          reconnect();
4223        }
4224    
4225        final LDAPConnectionInternals internals = connectionInternals;
4226        if (internals == null)
4227        {
4228          throw new LDAPException(ResultCode.SERVER_DOWN,
4229                                  ERR_CONN_NOT_ESTABLISHED.get());
4230        }
4231        else
4232        {
4233          internals.registerResponseAcceptor(messageID, responseAcceptor);
4234        }
4235      }
4236    
4237    
4238    
4239      /**
4240       * Deregisters the response acceptor associated with the provided message ID.
4241       *
4242       * @param  messageID  The message ID for which to deregister the associated
4243       *                    response acceptor.
4244       */
4245      void deregisterResponseAcceptor(final int messageID)
4246      {
4247        final LDAPConnectionInternals internals = connectionInternals;
4248        if (internals != null)
4249        {
4250          internals.deregisterResponseAcceptor(messageID);
4251        }
4252      }
4253    
4254    
4255    
4256      /**
4257       * Retrieves a timer for use with this connection, creating one if necessary.
4258       *
4259       * @return  A timer for use with this connection.
4260       */
4261      synchronized Timer getTimer()
4262      {
4263        if (timer == null)
4264        {
4265          timer = new Timer("Timer thread for " + toString(), true);
4266        }
4267    
4268        return timer;
4269      }
4270    
4271    
4272    
4273      /**
4274       * {@inheritDoc}
4275       */
4276      public LDAPConnection getReferralConnection(final LDAPURL referralURL,
4277                                                  final LDAPConnection connection)
4278             throws LDAPException
4279      {
4280        final String host = referralURL.getHost();
4281        final int    port = referralURL.getPort();
4282    
4283        BindRequest bindRequest = null;
4284        if (connection.lastBindRequest != null)
4285        {
4286          bindRequest = connection.lastBindRequest.getRebindRequest(host, port);
4287          if (bindRequest == null)
4288          {
4289            throw new LDAPException(ResultCode.REFERRAL,
4290                                    ERR_CONN_CANNOT_AUTHENTICATE_FOR_REFERRAL.get(
4291                                         host, port));
4292          }
4293        }
4294    
4295        final LDAPConnection conn = new LDAPConnection(connection.socketFactory,
4296             connection.connectionOptions, host, port);
4297    
4298        if (bindRequest != null)
4299        {
4300          try
4301          {
4302            conn.bind(bindRequest);
4303          }
4304          catch (LDAPException le)
4305          {
4306            debugException(le);
4307            conn.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
4308            conn.close();
4309    
4310            throw le;
4311          }
4312        }
4313    
4314        return conn;
4315      }
4316    
4317    
4318    
4319      /**
4320       * Retrieves the last successful bind request processed on this connection.
4321       *
4322       * @return  The last successful bind request processed on this connection.  It
4323       *          may be {@code null} if no bind has been performed, or if the last
4324       *          bind attempt was not successful.
4325       */
4326      BindRequest getLastBindRequest()
4327      {
4328        return lastBindRequest;
4329      }
4330    
4331    
4332    
4333      /**
4334       * Retrieves an instance of the {@code LDAPConnectionInternals} object for
4335       * this connection.
4336       *
4337       * @param  throwIfDisconnected  Indicates whether to throw an
4338       *                              {@code LDAPException} if the connection is not
4339       *                              established.
4340       *
4341       * @return  The {@code LDAPConnectionInternals} object for this connection, or
4342       *          {@code null} if the connection is not established and no exception
4343       *          should be thrown.
4344       *
4345       * @throws  LDAPException  If the connection is not established and
4346       *                         {@code throwIfDisconnected} is {@code true}.
4347       */
4348      LDAPConnectionInternals getConnectionInternals(
4349                                   final boolean throwIfDisconnected)
4350           throws LDAPException
4351      {
4352        final LDAPConnectionInternals internals = connectionInternals;
4353        if ((internals == null) && throwIfDisconnected)
4354        {
4355          throw new LDAPException(ResultCode.SERVER_DOWN,
4356               ERR_CONN_NOT_ESTABLISHED.get());
4357        }
4358        else
4359        {
4360          return internals;
4361        }
4362      }
4363    
4364    
4365    
4366      /**
4367       * Retrieves the cached schema for this connection, if applicable.
4368       *
4369       * @return  The cached schema for this connection, or {@code null} if it is
4370       *          not available (e.g., because the connection is not established,
4371       *          because {@link LDAPConnectionOptions#useSchema()} is false, or
4372       *          because an error occurred when trying to read the server schema).
4373       */
4374      Schema getCachedSchema()
4375      {
4376        return cachedSchema;
4377      }
4378    
4379    
4380    
4381      /**
4382       * Sets the cached schema for this connection.
4383       *
4384       * @param  cachedSchema  The cached schema for this connection.  It may be
4385       *                       {@code null} if no cached schema is available.
4386       */
4387      void setCachedSchema(final Schema cachedSchema)
4388      {
4389        this.cachedSchema = cachedSchema;
4390      }
4391    
4392    
4393    
4394      /**
4395       * Indicates whether this connection is operating in synchronous mode.
4396       *
4397       * @return  {@code true} if this connection is operating in synchronous mode,
4398       *          or {@code false} if not.
4399       */
4400      public boolean synchronousMode()
4401      {
4402        final LDAPConnectionInternals internals = connectionInternals;
4403        if (internals == null)
4404        {
4405          return false;
4406        }
4407        else
4408        {
4409          return internals.synchronousMode();
4410        }
4411      }
4412    
4413    
4414    
4415      /**
4416       * Reads a response from the server, blocking if necessary until the response
4417       * has been received.  This should only be used for connections operating in
4418       * synchronous mode.
4419       *
4420       * @param  messageID  The message ID for the response to be read.  Any
4421       *                    response read with a different message ID will be
4422       *                    discarded, unless it is an unsolicited notification in
4423       *                    which case it will be provided to any registered
4424       *                    unsolicited notification handler.
4425       *
4426       * @return  The response read from the server.
4427       *
4428       * @throws  LDAPException  If a problem occurs while reading the response.
4429       */
4430      LDAPResponse readResponse(final int messageID)
4431                   throws LDAPException
4432      {
4433        final LDAPConnectionInternals internals = connectionInternals;
4434        if (internals != null)
4435        {
4436          final LDAPResponse response =
4437               internals.getConnectionReader().readResponse(messageID);
4438          debugLDAPResult(response, this);
4439          return response;
4440        }
4441        else
4442        {
4443          final DisconnectInfo di = disconnectInfo.get();
4444          if (di == null)
4445          {
4446            return new ConnectionClosedResponse(ResultCode.CONNECT_ERROR,
4447                 ERR_CONN_READ_RESPONSE_NOT_ESTABLISHED.get());
4448          }
4449          else
4450          {
4451            return new ConnectionClosedResponse(di.getType().getResultCode(),
4452                 di.getMessage());
4453          }
4454        }
4455      }
4456    
4457    
4458    
4459      /**
4460       * Retrieves the time that this connection was established in the number of
4461       * milliseconds since January 1, 1970 UTC (the same format used by
4462       * {@code System.currentTimeMillis}.
4463       *
4464       * @return  The time that this connection was established, or -1 if the
4465       *          connection is not currently established.
4466       */
4467      public long getConnectTime()
4468      {
4469        final LDAPConnectionInternals internals = connectionInternals;
4470        if (internals != null)
4471        {
4472          return internals.getConnectTime();
4473        }
4474        else
4475        {
4476          return -1L;
4477        }
4478      }
4479    
4480    
4481    
4482      /**
4483       * Retrieves the time that this connection was last used to send or receive an
4484       * LDAP message.  The value will represent the number of milliseconds since
4485       * January 1, 1970 UTC (the same format used by
4486       * {@code System.currentTimeMillis}.
4487       *
4488       * @return  The time that this connection was last used to send or receive an
4489       *          LDAP message.  If the connection is not established, then -1 will
4490       *          be returned.  If the connection is established but no
4491       *          communication has been performed over the connection since it was
4492       *          established, then the value of {@link #getConnectTime()} will be
4493       *          returned.
4494       */
4495      public long getLastCommunicationTime()
4496      {
4497        if (lastCommunicationTime > 0L)
4498        {
4499          return lastCommunicationTime;
4500        }
4501        else
4502        {
4503          return getConnectTime();
4504        }
4505      }
4506    
4507    
4508    
4509      /**
4510       * Updates the last communication time for this connection to be the current
4511       * time.
4512       */
4513      void setLastCommunicationTime()
4514      {
4515        lastCommunicationTime = System.currentTimeMillis();
4516      }
4517    
4518    
4519    
4520      /**
4521       * Retrieves the connection statistics for this LDAP connection.
4522       *
4523       * @return  The connection statistics for this LDAP connection.
4524       */
4525      public LDAPConnectionStatistics getConnectionStatistics()
4526      {
4527        return connectionStatistics;
4528      }
4529    
4530    
4531    
4532      /**
4533       * Retrieves the number of outstanding operations on this LDAP connection
4534       * (i.e., the number of operations currently in progress).  The value will
4535       * only be valid for connections not configured to use synchronous mode.
4536       *
4537       * @return  The number of outstanding operations on this LDAP connection, or
4538       *          -1 if it cannot be determined (e.g., because the connection is not
4539       *          established or is operating in synchronous mode).
4540       */
4541      public int getActiveOperationCount()
4542      {
4543        final LDAPConnectionInternals internals = connectionInternals;
4544    
4545        if (internals == null)
4546        {
4547          return -1;
4548        }
4549        else
4550        {
4551          if (internals.synchronousMode())
4552          {
4553            return -1;
4554          }
4555          else
4556          {
4557            return internals.getConnectionReader().getActiveOperationCount();
4558          }
4559        }
4560      }
4561    
4562    
4563    
4564      /**
4565       * Retrieves the schema from the provided connection.  If the retrieved schema
4566       * matches schema that's already in use by other connections, the common
4567       * schema will be used instead of the newly-retrieved version.
4568       *
4569       * @param  c  The connection for which to retrieve the schema.
4570       *
4571       * @return  The schema retrieved from the given connection, or a cached
4572       *          schema if it matched a schema that was already in use.
4573       *
4574       * @throws  LDAPException  If a problem is encountered while retrieving or
4575       *                         parsing the schema.
4576       */
4577      private static Schema getCachedSchema(final LDAPConnection c)
4578             throws LDAPException
4579      {
4580        final Schema s = c.getSchema();
4581    
4582        synchronized (SCHEMA_SET)
4583        {
4584          return SCHEMA_SET.addAndGet(s);
4585        }
4586      }
4587    
4588    
4589    
4590      /**
4591       * Retrieves the connection attachment with the specified name.
4592       *
4593       * @param  name  The name of the attachment to retrieve.  It must not be
4594       *               {@code null}.
4595       *
4596       * @return  The connection attachment with the specified name, or {@code null}
4597       *          if there is no such attachment.
4598       */
4599      synchronized Object getAttachment(final String name)
4600      {
4601        if (attachments == null)
4602        {
4603          return null;
4604        }
4605        else
4606        {
4607          return attachments.get(name);
4608        }
4609      }
4610    
4611    
4612    
4613      /**
4614       * Sets a connection attachment with the specified name and value.
4615       *
4616       * @param  name   The name of the attachment to set.  It must not be
4617       *                {@code null}.
4618       * @param  value  The value to use for the attachment.  It may be {@code null}
4619       *                if an attachment with the specified name should be cleared
4620       *                rather than overwritten.
4621       */
4622      synchronized void setAttachment(final String name, final Object value)
4623      {
4624        if (attachments == null)
4625        {
4626          attachments = new HashMap<String,Object>(10);
4627        }
4628    
4629        if (value == null)
4630        {
4631          attachments.remove(name);
4632        }
4633        else
4634        {
4635          attachments.put(name, value);
4636        }
4637      }
4638    
4639    
4640    
4641      /**
4642       * Performs any necessary cleanup to ensure that this connection is properly
4643       * closed before it is garbage collected.
4644       *
4645       * @throws  Throwable  If the superclass finalizer throws an exception.
4646       */
4647      @Override()
4648      protected void finalize()
4649                throws Throwable
4650      {
4651        super.finalize();
4652    
4653        setDisconnectInfo(DisconnectType.CLOSED_BY_FINALIZER, null, null);
4654        setClosed();
4655      }
4656    
4657    
4658    
4659      /**
4660       * Retrieves a string representation of this LDAP connection.
4661       *
4662       * @return  A string representation of this LDAP connection.
4663       */
4664      @Override()
4665      public String toString()
4666      {
4667        final StringBuilder buffer = new StringBuilder();
4668        toString(buffer);
4669        return buffer.toString();
4670      }
4671    
4672    
4673    
4674      /**
4675       * Appends a string representation of this LDAP connection to the provided
4676       * buffer.
4677       *
4678       * @param  buffer  The buffer to which to append a string representation of
4679       *                 this LDAP connection.
4680       */
4681      public void toString(final StringBuilder buffer)
4682      {
4683        buffer.append("LDAPConnection(");
4684    
4685        final String name     = connectionName;
4686        final String poolName = connectionPoolName;
4687        if (name != null)
4688        {
4689          buffer.append("name='");
4690          buffer.append(name);
4691          buffer.append("', ");
4692        }
4693        else if (poolName != null)
4694        {
4695          buffer.append("poolName='");
4696          buffer.append(poolName);
4697          buffer.append("', ");
4698        }
4699    
4700        final LDAPConnectionInternals internals = connectionInternals;
4701        if ((internals != null) && internals.isConnected())
4702        {
4703          buffer.append("connected to ");
4704          buffer.append(internals.getHost());
4705          buffer.append(':');
4706          buffer.append(internals.getPort());
4707        }
4708        else
4709        {
4710          buffer.append("not connected");
4711        }
4712    
4713        buffer.append(')');
4714      }
4715    }